feat: new settings ui & features

translator, remove message tail, hide fast share, drawer customization, quick admin shortcuts, disable crash reports, donations
This commit is contained in:
AlexeyZavar
2025-07-29 05:02:23 +03:00
parent 1dfe68e9f3
commit dfb9ac91e9
78 changed files with 5144 additions and 1677 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ class ChannelData;
namespace Info::Profile {
class Badge;
enum class BadgeType : uchar;
enum class BadgeType : ushort;
} // namespace Info::Profile
namespace Main {
+7
View File
@@ -11,6 +11,7 @@
#include "ayu/ayu_ui_settings.h"
#include "ayu/ayu_worker.h"
#include "ayu/data/ayu_database.h"
#include "features/translator/ayu_translator.h"
#include "lang/lang_instance.h"
#include "utils/rc_manager.h"
@@ -32,6 +33,7 @@ void initUiSettings() {
AyuUiSettings::setMonoFont(settings.monoFont);
AyuUiSettings::setWideMultiplier(settings.wideMultiplier);
AyuUiSettings::setMaterialSwitches(settings.materialSwitches);
}
void initDatabase() {
@@ -46,12 +48,17 @@ void initRCManager() {
RCManager::getInstance().start();
}
void initTranslator() {
Ayu::Translator::TranslateManager::init();
}
void init() {
initLang();
initDatabase();
initUiSettings();
initWorker();
initRCManager();
initTranslator();
}
}
+93
View File
@@ -18,6 +18,7 @@
#include <fstream>
#include "ayu_worker.h"
#include "features/translator/ayu_translator.h"
#include "window/window_controller.h"
using json = nlohmann::json;
@@ -41,6 +42,8 @@ rpl::variable<QString> editedMarkReactive;
rpl::variable<int> showPeerIdReactive;
rpl::variable<QString> translationProviderReactive;
rpl::variable<bool> hideFromBlockedReactive;
rpl::event_stream<> historyUpdateReactive;
@@ -137,6 +140,7 @@ void postinitialize() {
deletedMarkReactive = settings->deletedMark;
editedMarkReactive = settings->editedMark;
showPeerIdReactive = settings->showPeerId;
translationProviderReactive = settings->translationProvider;
hideFromBlockedReactive = settings->hideFromBlocked;
@@ -194,6 +198,15 @@ void save() {
postinitialize();
}
void reset() {
lifetime.destroy();
lifetime = rpl::lifetime();
settings = std::nullopt;
initialize();
postinitialize();
save();
}
AyuGramSettings::AyuGramSettings() {
// ~ Ghost essentials
sendReadMessages = true;
@@ -229,6 +242,9 @@ AyuGramSettings::AyuGramSettings() {
increaseWebviewHeight = false;
increaseWebviewWidth = false;
materialSwitches = true;
removeMessageTail = false;
disableNotificationsDelay = false;
localPremium = false;
showChannelReactions = true;
@@ -243,6 +259,7 @@ AyuGramSettings::AyuGramSettings() {
#endif
;
simpleQuotesAndReplies = true;
hideFastShare = false;
replaceBottomInfoWithIcons = true;
deletedMark = "🧹";
editedMark = Core::IsAppLaunched() ? tr::lng_edited(tr::now) : QString("edited");
@@ -267,8 +284,17 @@ AyuGramSettings::AyuGramSettings() {
showAttachPopup = true;
showEmojiPopup = true;
// ~ Drawer Elements
showMyProfileInDrawer = true;
showBotsInDrawer = true;
showNewGroupInDrawer = true;
showNewChannelInDrawer = true;
showContactsInDrawer = true;
showCallsInDrawer = true;
showSavedMessagesInDrawer = true;
showLReadToggleInDrawer = false;
showSReadToggleInDrawer = true;
showNightModeToggleInDrawer = true;
showGhostToggleInDrawer = true;
showStreamerToggleInDrawer = false;
@@ -287,6 +313,7 @@ AyuGramSettings::AyuGramSettings() {
* channelBottomButton = 2 means "Discuss" + fallback to "Mute"/"Unmute"
*/
channelBottomButton = 2;
quickAdminShortcuts = true;
/*
* showPeerId = 0 means no ID shown
@@ -301,6 +328,10 @@ AyuGramSettings::AyuGramSettings() {
stickerConfirmation = false;
gifConfirmation = false;
voiceConfirmation = false;
translationProvider = "telegram"; // telegram, google, yandex
crashReporting = true;
}
void set_sendReadMessages(bool val) {
@@ -411,6 +442,14 @@ void set_increaseWebviewWidth(bool val) {
settings->increaseWebviewWidth = val;
}
void set_materialSwitches(bool val) {
settings->materialSwitches = val;
}
void set_removeMessageTail(bool val) {
settings->removeMessageTail = val;
}
void set_disableNotificationsDelay(bool val) {
settings->disableNotificationsDelay = val;
}
@@ -435,6 +474,10 @@ void set_simpleQuotesAndReplies(bool val) {
settings->simpleQuotesAndReplies = val;
}
void set_hideFastShare(bool val) {
settings->hideFastShare = val;
}
void set_replaceBottomInfoWithIcons(bool val) {
settings->replaceBottomInfoWithIcons = val;
}
@@ -508,6 +551,34 @@ void set_showEmojiPopup(bool val) {
triggerHistoryUpdate();
}
void set_showMyProfileInDrawer(bool val) {
settings->showMyProfileInDrawer = val;
}
void set_showBotsInDrawer(bool val) {
settings->showBotsInDrawer = val;
}
void set_showNewGroupInDrawer(bool val) {
settings->showNewGroupInDrawer = val;
}
void set_showNewChannelInDrawer(bool val) {
settings->showNewChannelInDrawer = val;
}
void set_showContactsInDrawer(bool val) {
settings->showContactsInDrawer = val;
}
void set_showCallsInDrawer(bool val) {
settings->showCallsInDrawer = val;
}
void set_showSavedMessagesInDrawer(bool val) {
settings->showSavedMessagesInDrawer = val;
}
void set_showLReadToggleInDrawer(bool val) {
settings->showLReadToggleInDrawer = val;
}
@@ -516,6 +587,10 @@ void set_showSReadToggleInDrawer(bool val) {
settings->showSReadToggleInDrawer = val;
}
void set_showNightModeToggleInDrawer(bool val) {
settings->showNightModeToggleInDrawer = val;
}
void set_showGhostToggleInDrawer(bool val) {
settings->showGhostToggleInDrawer = val;
}
@@ -557,6 +632,10 @@ void set_channelBottomButton(int val) {
settings->channelBottomButton = val;
}
void set_quickAdminShortcuts(bool val) {
settings->quickAdminShortcuts = val;
}
void set_showMessageSeconds(bool val) {
settings->showMessageSeconds = val;
}
@@ -577,6 +656,16 @@ void set_voiceConfirmation(bool val) {
settings->voiceConfirmation = val;
}
void set_translationProvider(const QString &val) {
settings->translationProvider = val;
translationProviderReactive = val;
Ayu::Translator::TranslateManager::currentInstance()->resetCache();
}
void set_crashReporting(bool val) {
settings->crashReporting = val;
}
bool isUseScheduledMessages() {
return isGhostModeActive() && settings->useScheduledMessages;
}
@@ -597,6 +686,10 @@ rpl::producer<int> get_showPeerIdReactive() {
return showPeerIdReactive.value();
}
rpl::producer<QString> get_translationProviderReactive() {
return translationProviderReactive.value();
}
rpl::producer<bool> get_ghostModeEnabledReactive() {
return ghostModeEnabled.value();
}
+65
View File
@@ -74,6 +74,9 @@ public:
bool increaseWebviewHeight;
bool increaseWebviewWidth;
bool materialSwitches;
bool removeMessageTail;
bool disableNotificationsDelay;
bool localPremium;
bool showChannelReactions;
@@ -81,6 +84,7 @@ public:
QString appIcon;
bool simpleQuotesAndReplies;
bool hideFastShare;
bool replaceBottomInfoWithIcons;
QString deletedMark;
QString editedMark;
@@ -101,8 +105,16 @@ public:
bool showAttachPopup;
bool showEmojiPopup;
bool showMyProfileInDrawer;
bool showBotsInDrawer;
bool showNewGroupInDrawer;
bool showNewChannelInDrawer;
bool showContactsInDrawer;
bool showCallsInDrawer;
bool showSavedMessagesInDrawer;
bool showLReadToggleInDrawer;
bool showSReadToggleInDrawer;
bool showNightModeToggleInDrawer;
bool showGhostToggleInDrawer;
bool showStreamerToggleInDrawer;
@@ -116,6 +128,7 @@ public:
bool hideAllChatsFolder;
int channelBottomButton;
bool quickAdminShortcuts;
int showPeerId;
bool showMessageSeconds;
@@ -124,6 +137,10 @@ public:
bool stickerConfirmation;
bool gifConfirmation;
bool voiceConfirmation;
QString translationProvider;
bool crashReporting;
};
void set_sendReadMessages(bool val);
@@ -158,6 +175,9 @@ void set_spoofWebviewAsAndroid(bool val);
void set_increaseWebviewHeight(bool val);
void set_increaseWebviewWidth(bool val);
void set_materialSwitches(bool val);
void set_removeMessageTail(bool val);
void set_disableNotificationsDelay(bool val);
void set_localPremium(bool val);
void set_hideChannelReactions(bool val);
@@ -165,6 +185,7 @@ void set_hideGroupReactions(bool val);
void set_appIcon(const QString &val);
void set_simpleQuotesAndReplies(bool val);
void set_hideFastShare(bool val);
void set_replaceBottomInfoWithIcons(bool val);
void set_deletedMark(const QString &val);
void set_editedMark(const QString &val);
@@ -185,8 +206,16 @@ void set_showAutoDeleteButtonInMessageField(bool val);
void set_showAttachPopup(bool val);
void set_showEmojiPopup(bool val);
void set_showMyProfileInDrawer(bool val);
void set_showBotsInDrawer(bool val);
void set_showNewGroupInDrawer(bool val);
void set_showNewChannelInDrawer(bool val);
void set_showContactsInDrawer(bool val);
void set_showCallsInDrawer(bool val);
void set_showSavedMessagesInDrawer(bool val);
void set_showLReadToggleInDrawer(bool val);
void set_showSReadToggleInDrawer(bool val);
void set_showNightModeToggleInDrawer(bool val);
void set_showGhostToggleInDrawer(bool val);
void set_showStreamerToggleInDrawer(bool val);
@@ -200,6 +229,7 @@ void set_hideNotificationBadge(bool val);
void set_hideAllChatsFolder(bool val);
void set_channelBottomButton(int val);
void set_quickAdminShortcuts(bool val);
void set_showPeerId(int val);
void set_showMessageSeconds(bool val);
@@ -209,6 +239,10 @@ void set_stickerConfirmation(bool val);
void set_gifConfirmation(bool val);
void set_voiceConfirmation(bool val);
void set_translationProvider(const QString &val);
void set_crashReporting(bool val);
inline void to_json(nlohmann::json &nlohmann_json_j, const AyuGramSettings &nlohmann_json_t) {
NLOHMANN_JSON_TO(sendReadMessages)
NLOHMANN_JSON_TO(sendReadStories)
@@ -232,10 +266,13 @@ inline void to_json(nlohmann::json &nlohmann_json_j, const AyuGramSettings &nloh
NLOHMANN_JSON_TO(spoofWebviewAsAndroid)
NLOHMANN_JSON_TO(increaseWebviewHeight)
NLOHMANN_JSON_TO(increaseWebviewWidth)
NLOHMANN_JSON_TO(materialSwitches)
NLOHMANN_JSON_TO(removeMessageTail)
NLOHMANN_JSON_TO(disableNotificationsDelay)
NLOHMANN_JSON_TO(localPremium)
NLOHMANN_JSON_TO(appIcon)
NLOHMANN_JSON_TO(simpleQuotesAndReplies)
NLOHMANN_JSON_TO(hideFastShare)
NLOHMANN_JSON_TO(replaceBottomInfoWithIcons)
NLOHMANN_JSON_TO(deletedMark)
NLOHMANN_JSON_TO(editedMark)
@@ -252,8 +289,16 @@ inline void to_json(nlohmann::json &nlohmann_json_j, const AyuGramSettings &nloh
NLOHMANN_JSON_TO(showAutoDeleteButtonInMessageField)
NLOHMANN_JSON_TO(showAttachPopup)
NLOHMANN_JSON_TO(showEmojiPopup)
NLOHMANN_JSON_TO(showMyProfileInDrawer)
NLOHMANN_JSON_TO(showBotsInDrawer)
NLOHMANN_JSON_TO(showNewGroupInDrawer)
NLOHMANN_JSON_TO(showNewChannelInDrawer)
NLOHMANN_JSON_TO(showContactsInDrawer)
NLOHMANN_JSON_TO(showCallsInDrawer)
NLOHMANN_JSON_TO(showSavedMessagesInDrawer)
NLOHMANN_JSON_TO(showLReadToggleInDrawer)
NLOHMANN_JSON_TO(showSReadToggleInDrawer)
NLOHMANN_JSON_TO(showNightModeToggleInDrawer)
NLOHMANN_JSON_TO(showGhostToggleInDrawer)
NLOHMANN_JSON_TO(showStreamerToggleInDrawer)
NLOHMANN_JSON_TO(showGhostToggleInTray)
@@ -265,12 +310,15 @@ inline void to_json(nlohmann::json &nlohmann_json_j, const AyuGramSettings &nloh
NLOHMANN_JSON_TO(hideNotificationBadge)
NLOHMANN_JSON_TO(hideAllChatsFolder)
NLOHMANN_JSON_TO(channelBottomButton)
NLOHMANN_JSON_TO(quickAdminShortcuts)
NLOHMANN_JSON_TO(showPeerId)
NLOHMANN_JSON_TO(showMessageSeconds)
NLOHMANN_JSON_TO(showMessageShot)
NLOHMANN_JSON_TO(stickerConfirmation)
NLOHMANN_JSON_TO(gifConfirmation)
NLOHMANN_JSON_TO(voiceConfirmation)
NLOHMANN_JSON_TO(translationProvider)
NLOHMANN_JSON_TO(crashReporting)
}
inline void from_json(const nlohmann::json &nlohmann_json_j, AyuGramSettings &nlohmann_json_t) {
@@ -297,10 +345,13 @@ inline void from_json(const nlohmann::json &nlohmann_json_j, AyuGramSettings &nl
NLOHMANN_JSON_FROM_WITH_DEFAULT(spoofWebviewAsAndroid)
NLOHMANN_JSON_FROM_WITH_DEFAULT(increaseWebviewHeight)
NLOHMANN_JSON_FROM_WITH_DEFAULT(increaseWebviewWidth)
NLOHMANN_JSON_FROM_WITH_DEFAULT(materialSwitches)
NLOHMANN_JSON_FROM_WITH_DEFAULT(removeMessageTail)
NLOHMANN_JSON_FROM_WITH_DEFAULT(disableNotificationsDelay)
NLOHMANN_JSON_FROM_WITH_DEFAULT(localPremium)
NLOHMANN_JSON_FROM_WITH_DEFAULT(appIcon)
NLOHMANN_JSON_FROM_WITH_DEFAULT(simpleQuotesAndReplies)
NLOHMANN_JSON_FROM_WITH_DEFAULT(hideFastShare)
NLOHMANN_JSON_FROM_WITH_DEFAULT(replaceBottomInfoWithIcons)
NLOHMANN_JSON_FROM_WITH_DEFAULT(deletedMark)
NLOHMANN_JSON_FROM_WITH_DEFAULT(editedMark)
@@ -317,8 +368,16 @@ inline void from_json(const nlohmann::json &nlohmann_json_j, AyuGramSettings &nl
NLOHMANN_JSON_FROM_WITH_DEFAULT(showAutoDeleteButtonInMessageField)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showAttachPopup)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showEmojiPopup)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showMyProfileInDrawer)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showBotsInDrawer)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showNewGroupInDrawer)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showNewChannelInDrawer)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showContactsInDrawer)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showCallsInDrawer)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showSavedMessagesInDrawer)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showLReadToggleInDrawer)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showSReadToggleInDrawer)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showNightModeToggleInDrawer)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showGhostToggleInDrawer)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showStreamerToggleInDrawer)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showGhostToggleInTray)
@@ -330,24 +389,30 @@ inline void from_json(const nlohmann::json &nlohmann_json_j, AyuGramSettings &nl
NLOHMANN_JSON_FROM_WITH_DEFAULT(hideNotificationBadge)
NLOHMANN_JSON_FROM_WITH_DEFAULT(hideAllChatsFolder)
NLOHMANN_JSON_FROM_WITH_DEFAULT(channelBottomButton)
NLOHMANN_JSON_FROM_WITH_DEFAULT(quickAdminShortcuts)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showPeerId)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showMessageSeconds)
NLOHMANN_JSON_FROM_WITH_DEFAULT(showMessageShot)
NLOHMANN_JSON_FROM_WITH_DEFAULT(stickerConfirmation)
NLOHMANN_JSON_FROM_WITH_DEFAULT(gifConfirmation)
NLOHMANN_JSON_FROM_WITH_DEFAULT(voiceConfirmation)
NLOHMANN_JSON_FROM_WITH_DEFAULT(translationProvider)
NLOHMANN_JSON_FROM_WITH_DEFAULT(crashReporting)
}
AyuGramSettings &getInstance();
void load();
void save();
void reset();
rpl::producer<QString> get_deletedMarkReactive();
rpl::producer<QString> get_editedMarkReactive();
rpl::producer<int> get_showPeerIdReactive();
rpl::producer<QString> get_translationProviderReactive();
bool isGhostModeActive();
bool isUseScheduledMessages();
+37 -1
View File
@@ -20,6 +20,9 @@
#include <QDesktopServices>
#include "main/main_session.h"
#include "ui/boxes/donate_info_box.h"
#include "ui/settings/settings_main.h"
#include "window/window_controller.h"
namespace AyuUrlHandlers {
@@ -68,7 +71,40 @@ bool HandleAyu(
if (!controller) {
return false;
}
controller->showToast(QString(":3"), 500);
try {
const auto section = match->captured(1).mid(1).toLower();
const auto type = [&]() -> std::optional<::Settings::Type>
{
if (section == u"settings"_q || section == u"preferences"_q || section == u"prefs"_q) {
return ::Settings::AyuMain::Id();
}
return std::nullopt;
}();
if (type.has_value()) {
controller->showSettings(*type);
controller->window().activate();
} else {
controller->showToast(QString(":3"), 500);
}
} catch (...) {
}
return true;
}
bool HandleSupport(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
if (!controller) {
return false;
}
auto box = Box(
Ui::FillDonateInfoBox,
controller);
Ui::show(std::move(box));
return true;
}
@@ -23,6 +23,11 @@ bool HandleAyu(
const Match &match,
const QVariant &context);
bool HandleSupport(
Window::SessionController *controller,
const Match &match,
const QVariant &context);
bool TryHandleSpotify(const QString &url);
}
@@ -6,8 +6,8 @@
// Copyright @Radolyn, 2025
#include "message_shot.h"
#include "styles/style_layers.h"
#include "styles/style_ayu_styles.h"
#include "styles/style_layers.h"
#include "qguiapplication.h"
#include "ayu/ui/boxes/message_shot_box.h"
@@ -20,8 +20,8 @@
#include "history/history.h"
#include "history/history_inner_widget.h"
#include "history/history_item.h"
#include "history/view/history_view_element.h"
#include "history/history_item_components.h"
#include "history/view/history_view_element.h"
#include "history/view/media/history_view_media.h"
#include "main/main_session.h"
#include "styles/style_chat.h"
@@ -0,0 +1,332 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "ayu_translator.h"
#include <optional>
#include <QtCore/QCryptographicHash>
#include <QtCore/QString>
#include <QtNetwork/QNetworkReply>
#include "api/api_text_entities.h"
#include "ayu/ayu_settings.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "history/history_item.h"
#include "implementations/google.h"
#include "implementations/telegram.h"
#include "implementations/yandex.h"
#include "main/main_session.h"
// todo: expose available languages from current translator and use in `ChooseTranslateToBox`
namespace Ayu::Translator {
TranslateManager::Builder::Builder(
TranslateManager &manager,
Main::Session &session,
const MTPflags<MTPmessages_translateText::Flags> &flags,
const MTPInputPeer &peer,
const MTPVector<MTPint> &id,
const MTPVector<MTPTextWithEntities> &text,
const MTPstring &to_lang
)
: _manager(&manager)
, _session(&session)
, _flags(flags)
, _peer(peer)
, _idList(id)
, _text(text)
, _toLang(to_lang) {
}
TranslateManager::Builder &TranslateManager::Builder::done(std::function < void(const Result &) > cb) {
_done = std::move(cb);
return *this;
}
TranslateManager::Builder &TranslateManager::Builder::fail(std::function < void(const MTP::Error &) > cb) {
_fail = std::move(cb);
return *this;
}
TranslateManager::Builder &TranslateManager::Builder::fail(std::function<void()> cb) {
_fail = [cb = std::move(cb)](const MTP::Error &)
{
cb();
};
return *this;
}
mtpRequestId TranslateManager::Builder::send() {
return _manager->performTranslation(*this);
}
void TranslateManager::Builder::cancel() {
if (_id) {
_manager->cancel(_id);
_id = 0;
}
}
TranslateManager::Builder TranslateManager::request(
Main::Session &session,
const MTPflags<MTPmessages_translateText::Flags> &flags,
const MTPInputPeer &peer,
const MTPVector<MTPint> &id,
const MTPVector<MTPTextWithEntities> &text,
const MTPstring &to_lang
) {
return Builder(*this, session, flags, peer, id, text, to_lang);
}
mtpRequestId TranslateManager::performTranslation(Builder &req) {
const auto id = _nextId++;
_pending.emplace(id,
Pending{
.done = std::move(req._done),
.fail = std::move(req._fail),
.cancel = nullptr,
});
req._id = id;
std::vector<TextWithEntities> texts;
std::vector<QString> cacheKeys;
std::vector<TextWithEntities> resultTexts;
std::vector<int> uncachedIndices;
std::vector<TextWithEntities> uncachedTexts;
const auto toLang = qs(req._toLang);
const auto fromLang = QStringLiteral("auto");
if (!req.texts().v.isEmpty()) {
for (int i = 0; i < req.texts().v.size(); ++i) {
const auto text = qs(req.texts().v[i].data().vtext());
const auto entities = Api::EntitiesFromMTP(req.session(), req.texts().v[i].data().ventities().v);
const auto textWithEntities = TextWithEntities{
.text = text,
.entities = entities
};
texts.push_back(textWithEntities);
// todo: entities are not considered in cache key
const auto key = generateCacheKey(text, fromLang, toLang);
cacheKeys.push_back(key);
if (const auto cached = getFromCache(key)) {
resultTexts.push_back(cached->translatedText);
} else {
resultTexts.push_back({});
uncachedIndices.push_back(i);
uncachedTexts.push_back(textWithEntities);
}
}
} else if (!req.ids().v.isEmpty()) {
if (const auto peerData = Data::PeerFromInputMTP(&req.session()->data(), req.peer())) {
for (int i = 0; i < req.ids().v.size(); ++i) {
const auto msgId = req.ids().v[i].v;
if (const auto message = req.session()->data().message(peerData->id, msgId)) {
const auto textWithEntities = message->originalText();
texts.push_back(textWithEntities);
const auto key = generateMessageCacheKey(peerData->id, msgId, fromLang, toLang);
cacheKeys.push_back(key);
if (const auto cached = getFromCache(key)) {
resultTexts.push_back(cached->translatedText);
} else {
resultTexts.push_back({});
uncachedIndices.push_back(i);
uncachedTexts.push_back(textWithEntities);
}
} else {
// todo: ??
texts.push_back({});
cacheKeys.push_back(QString());
resultTexts.push_back({});
}
}
}
}
if (texts.empty() || toLang.isEmpty()) {
triggerFail(id);
return id;
}
if (uncachedTexts.empty()) {
auto vec = QVector<MTPTextWithEntities>();
for (const auto &translatedText : resultTexts) {
vec.push_back(MTP_textWithEntities(
MTP_string(translatedText.text),
Api::EntitiesToMTP(req.session(), translatedText.entities)));
}
const auto result = MTP_messages_translateResult(MTP_vector<MTPTextWithEntities>(vec));
triggerDone(id, result);
return id;
}
CallbackSuccess onSuccess = [this, id, resultTexts = std::move(resultTexts), cacheKeys = std::move(cacheKeys),
uncachedIndices = std::move(uncachedIndices), texts = std::move(texts),
fromLang, toLang, &req](const std::vector<TextWithEntities> &translated) mutable
{
for (size_t i = 0; i < translated.size() && i < uncachedIndices.size(); ++i) {
const auto index = uncachedIndices[i];
resultTexts[index] = translated[i];
const auto &key = cacheKeys[index];
if (!key.isEmpty()) {
insertToCache(key,
CacheEntry{
.originalText = texts[index],
.translatedText = translated[i],
.fromLang = fromLang,
.toLang = toLang
});
}
}
auto vec = QVector<MTPTextWithEntities>();
for (const auto &translatedText : resultTexts) {
vec.push_back(MTP_textWithEntities(
MTP_string(translatedText.text),
Api::EntitiesToMTP(req.session(), translatedText.entities)));
}
const auto result = MTP_messages_translateResult(MTP_vector<MTPTextWithEntities>(vec));
triggerDone(id, result);
};
CallbackFail onFail = [this, id]
{
triggerFail(id);
};
const auto args = StartTranslationArgs{
.session = req.session(),
.requestData = {
.flags = req.flags(),
.peer = req.peer(),
.idList = req.ids(),
.text = req.texts(),
.toLang = req.toLang(),
},
.parsedData = {
.texts = uncachedTexts,
.fromLang = fromLang,
.toLang = toLang,
},
.onSuccess = std::move(onSuccess),
.onFail = std::move(onFail),
};
if (const auto it = _pending.find(id); it != _pending.end()) {
const auto &settings = AyuSettings::getInstance();
if (settings.translationProvider == "telegram") {
it->second.cancel = TelegramTranslator::instance().startTranslation(args);
} else if (settings.translationProvider == "yandex") {
it->second.cancel = YandexTranslator::instance().startTranslation(args);
} else {
it->second.cancel = GoogleTranslator::instance().startTranslation(args);
}
}
return id;
}
bool TranslateManager::cancel(mtpRequestId requestId) {
const auto it = _pending.find(requestId);
if (it == _pending.end()) return false;
if (it->second.cancel) {
it->second.cancel();
}
_pending.erase(it);
return true;
}
bool TranslateManager::triggerDone(mtpRequestId id, const Result &result) {
const auto it = _pending.find(id);
if (it == _pending.end()) return false;
auto cb = std::move(it->second.done);
_pending.erase(it);
if (cb) cb(result);
return true;
}
bool TranslateManager::triggerFail(mtpRequestId id) {
const auto it = _pending.find(id);
if (it == _pending.end()) return false;
auto cb = std::move(it->second.fail);
_pending.erase(it);
if (cb) cb(MTP::Error(MTP::Error::MTPLocal("RESPONSE_PARSE_FAILED", "Error parse failed.")));
return true;
}
void TranslateManager::resetCache() {
_cacheList.clear();
_cacheMap.clear();
// todo: remove all running requests
}
TranslateManager *TranslateManager::currentInstance() {
return instance;
}
TranslateManager *TranslateManager::instance = nullptr;
void TranslateManager::init() {
if (!instance) instance = new TranslateManager;
}
QString TranslateManager::generateCacheKey(const QString &text, const QString &fromLang, const QString &toLang) const {
const auto textHash = QCryptographicHash::hash(text.toUtf8(), QCryptographicHash::Sha1).toHex();
return QStringLiteral("%1_%2_%3").arg(QString::fromLatin1(textHash), fromLang, toLang);
}
QString TranslateManager::generateMessageCacheKey(PeerId peerId,
MsgId msgId,
const QString &fromLang,
const QString &toLang) const {
return QStringLiteral("%1_%2_%3_%4").arg(peerId.value).arg(msgId.bare).arg(fromLang, toLang);
}
void TranslateManager::insertToCache(const QString &key, const CacheEntry &entry) {
if (const auto it = _cacheMap.find(key); it != _cacheMap.end()) {
_cacheList.erase(it->second);
_cacheMap.erase(it);
}
_cacheList.emplace_front(key, entry);
_cacheMap[key] = _cacheList.begin();
if (_cacheList.size() > MAX_CACHE_SIZE) {
removeLeastRecentlyUsed();
}
}
std::optional<TranslateManager::CacheEntry> TranslateManager::getFromCache(const QString &key) {
const auto it = _cacheMap.find(key);
if (it == _cacheMap.end()) {
return std::nullopt;
}
auto entry = it->second->second;
_cacheList.erase(it->second);
_cacheList.emplace_front(key, entry);
_cacheMap[key] = _cacheList.begin();
return entry;
}
void TranslateManager::removeLeastRecentlyUsed() {
if (_cacheList.empty()) return;
const auto &lru = _cacheList.back();
_cacheMap.erase(lru.first);
_cacheList.pop_back();
}
}
@@ -0,0 +1,137 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
#include "mtproto/sender.h"
#include <functional>
#include <list>
#include <unordered_map>
#include <QtCore/QString>
#include "implementations/base.h"
class QNetworkReply;
namespace Main {
class Session;
}
namespace Ayu::Translator {
class TranslateManager
{
public:
using Request = MTPmessages_TranslateText;
using Result = Request::ResponseType;
class Builder
{
public:
Builder(
TranslateManager &manager,
Main::Session &session,
const MTPflags<MTPmessages_translateText::Flags> &flags,
const MTPInputPeer &peer,
const MTPVector<MTPint> &id,
const MTPVector<MTPTextWithEntities> &text,
const MTPstring &to_lang
);
Builder(Builder &&) noexcept = default;
Builder &operator=(Builder &&) noexcept = default;
Builder &done(std::function<void(const Result &)> cb);
Builder &fail(std::function<void(const MTP::Error &)> cb);
Builder &fail(std::function<void()> cb);
mtpRequestId send();
void cancel();
[[nodiscard]] const auto &session() const { return _session; }
[[nodiscard]] const auto &flags() const { return _flags; }
[[nodiscard]] const auto &peer() const { return _peer; }
[[nodiscard]] const auto &ids() const { return _idList; }
[[nodiscard]] const auto &texts() const { return _text; }
[[nodiscard]] const auto &toLang() const { return _toLang; }
private:
TranslateManager *_manager = nullptr;
not_null<Main::Session*> _session;
mtpRequestId _id = 0;
MTPflags<MTPmessages_translateText::Flags> _flags;
MTPInputPeer _peer;
MTPVector<MTPint> _idList;
MTPVector<MTPTextWithEntities> _text;
MTPstring _toLang;
std::function<void(const Result &)> _done;
std::function<void(const MTP::Error &)> _fail;
friend class TranslateManager;
};
TranslateManager() = default;
~TranslateManager() = default;
Builder request(
Main::Session &session,
const MTPflags<MTPmessages_translateText::Flags> &flags,
const MTPInputPeer &peer,
const MTPVector<MTPint> &id,
const MTPVector<MTPTextWithEntities> &text,
const MTPstring &to_lang
);
[[nodiscard]] mtpRequestId performTranslation(Builder &req);
bool cancel(mtpRequestId requestId);
bool triggerDone(mtpRequestId id, const Result &result);
bool triggerFail(mtpRequestId id);
void resetCache();
static TranslateManager *currentInstance();
static void init();
static TranslateManager *instance;
private:
struct CacheEntry
{
TextWithEntities originalText;
TextWithEntities translatedText;
QString fromLang;
QString toLang;
};
using CacheKey = QString;
using CacheIterator = std::list<std::pair<CacheKey, CacheEntry>>::iterator;
std::list<std::pair<CacheKey, CacheEntry>> _cacheList;
std::unordered_map<CacheKey, CacheIterator> _cacheMap;
static constexpr size_t MAX_CACHE_SIZE = 500;
QString generateCacheKey(const QString &text, const QString &fromLang, const QString &toLang) const;
QString generateMessageCacheKey(PeerId peerId, MsgId msgId, const QString &fromLang, const QString &toLang) const;
void insertToCache(const QString &key, const CacheEntry &entry);
std::optional<CacheEntry> getFromCache(const QString &key);
void removeLeastRecentlyUsed();
struct Pending
{
std::function<void(const Result &)> done;
std::function<void(const MTP::Error &)> fail;
CallbackCancel cancel;
};
mtpRequestId _nextId = 1;
std::unordered_map<mtpRequestId, Pending> _pending;
};
} // namespace Ayu::Translator
@@ -0,0 +1,29 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "html_parser.h"
namespace Ayu::Translator::Html {
// yandex messes up HTML badly, so formatting removed for now
QString entitiesToHtml(const TextWithEntities &text) {
return text.text;
}
TextWithEntities htmlToEntities(const QString &text) {
TextWithEntities result = {.text = text};
// links parsing doesn't work actually as it's not even accounted in ParseEntities
// todo: find a way to parse links
TextUtilities::ApplyServerCleaning(result);
TextUtilities::ParseEntities(result, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands);
TextUtilities::Trim(result);
return result;
}
}
@@ -0,0 +1,17 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
#include <QtCore/QString>
#include "ui/text/text_entity.h"
namespace Ayu::Translator::Html {
[[nodiscard]] QString entitiesToHtml(const TextWithEntities &text);
[[nodiscard]] TextWithEntities htmlToEntities(const QString &text);
}
@@ -0,0 +1,305 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
#include <QtCore/QJsonArray>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonValue>
#include <QtCore/QString>
#include <QtCore/QTimer>
#include "base/random.h"
#include <cmath>
#include <memory>
#include <vector>
#include "./base.h"
namespace Ayu::Translator {
std::vector<QString> desktopUserAgents = {
// zen
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:142.0) Gecko/20100101 Firefox/142.0",
// cent
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
// orion mac
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15",
// chrome mac
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36"
};
QString randomDesktopUserAgent() {
return desktopUserAgents[base::RandomIndex(static_cast<int>(desktopUserAgents.size()))];
}
bool shouldWrapInHtml() {
// todo: make an option
return true;
}
QString parseJsonPath(const QByteArray &body, const QString &jsonPath, bool *ok) {
if (ok) *ok = false;
if (body.isEmpty()) {
return {};
}
QJsonParseError err{};
const auto doc = QJsonDocument::fromJson(body, &err);
if (err.error != QJsonParseError::NoError || doc.isNull()) {
return {};
}
QJsonValue current;
if (doc.isObject()) {
current = doc.object();
} else if (doc.isArray()) {
current = doc.array();
} else {
return {};
}
auto indexInto = [](const QJsonValue &val, int idx, bool *okPtr) -> QJsonValue
{
if (!val.isArray()) {
if (okPtr) *okPtr = false;
return QJsonValue{};
}
const auto arr = val.toArray();
if (idx < 0 || idx >= arr.size()) {
if (okPtr) *okPtr = false;
return QJsonValue{};
}
if (okPtr) *okPtr = true;
return arr.at(idx);
};
const auto parts = jsonPath.split('.', Qt::SkipEmptyParts);
for (const auto &partRaw : parts) {
QString part = partRaw;
int pos = 0;
if (!part.isEmpty() && part[0] != '[') {
const int bracket = part.indexOf('[');
QString key = (bracket >= 0) ? part.left(bracket) : part;
pos = key.size();
if (!key.isEmpty()) {
if (!current.isObject()) {
return {};
}
current = current.toObject().value(key);
if (current.isUndefined() || current.isNull()) {
return {};
}
}
}
while (pos < part.size() && part[pos] == '[') {
const int end = part.indexOf(']', pos + 1);
if (end < 0) return {};
const auto idxStr = part.mid(pos + 1, end - pos - 1);
bool okIndex = false;
int idx = idxStr.toInt(&okIndex);
if (!okIndex) return {};
bool okStep = false;
current = indexInto(current, idx, &okStep);
if (!okStep) return {};
pos = end + 1;
}
}
QString result;
if (current.isString()) {
result = current.toString();
} else if (current.isArray()) {
const auto arr = current.toArray();
result.reserve(256);
for (const auto &v : arr) {
if (v.isObject()) {
const auto o = v.toObject();
if (o.contains("trans")) {
result += o.value("trans").toString();
} else if (o.contains("text")) {
result += o.value("text").toString();
}
} else if (v.isString()) {
result += v.toString();
}
}
} else if (current.isObject()) {
const auto o = current.toObject();
if (o.contains("trans")) {
result = o.value("trans").toString();
} else if (o.contains("text")) {
result = o.value("text").toString();
}
}
if (ok) *ok = !result.isNull();
return result;
}
CallbackCancel MultiThreadTranslator::startTranslation(const StartTranslationArgs &args) {
const auto &texts = args.parsedData.texts;
const auto &fromLang = args.parsedData.fromLang;
const auto &toLang = args.parsedData.toLang;
if (texts.empty() || toLang.trimmed().isEmpty()) {
if (args.onFail) args.onFail();
return []
{
};
}
struct BatchState
{
MultiThreadTranslator *self = nullptr;
std::vector<TextWithEntities> inputs;
QString from;
QString to;
CallbackSuccess onSuccess;
CallbackFail onFail;
std::vector<TextWithEntities> results;
std::vector<QPointer<QNetworkReply>> replies;
std::vector<int> retryCount;
std::vector<QPointer<QTimer>> retryTimers;
int total = 0;
int nextIndex = 0;
int inProgress = 0;
bool finished = false;
std::function<void()> pump;
std::function<void(int)> tryTranslateIndex;
void cancelAll() const {
for (auto &r : replies) {
if (r && r->isRunning()) {
r->abort();
}
}
for (auto &timer : retryTimers) {
if (timer) {
timer->stop();
}
}
}
};
auto state = std::make_shared<BatchState>();
state->self = this;
state->inputs = texts;
state->from = fromLang;
state->to = toLang;
state->onSuccess = args.onSuccess;
state->onFail = args.onFail;
state->total = static_cast<int>(texts.size());
state->results.resize(state->total);
state->replies.resize(state->total);
state->retryCount.resize(state->total, 0);
state->retryTimers.resize(state->total);
const auto maxConcurrent = getConcurrencyLimit();
const auto maxRetries = getMaxRetries();
const auto baseWaitTime = getBaseWaitTimeMs();
auto finishFail = [state]()
{
if (state->finished) return;
state->finished = true;
state->cancelAll();
if (state->onFail) state->onFail();
};
auto finishSuccess = [state]()
{
if (state->finished) return;
state->finished = true;
if (state->onSuccess) state->onSuccess(state->results);
};
state->tryTranslateIndex = [state, finishFail, finishSuccess, maxRetries, baseWaitTime](int i) mutable
{
if (state->finished) return;
MultiThreadArgs singleArgs;
singleArgs.parsedData.text = state->inputs[i];
singleArgs.parsedData.fromLang = state->from;
singleArgs.parsedData.toLang = state->to;
singleArgs.onSuccess = [state, i, finishSuccess](const TextWithEntities &translated) mutable
{
if (state->finished) return;
state->results[i] = translated;
state->replies[i] = nullptr;
state->inProgress--;
if (state->nextIndex >= state->total && state->inProgress == 0) {
finishSuccess();
return;
}
if (!state->finished && state->pump) {
state->pump();
}
};
singleArgs.onFail = [state, i, finishFail, maxRetries, baseWaitTime]() mutable
{
if (state->finished) return;
state->replies[i] = nullptr;
state->retryCount[i]++;
if (state->retryCount[i] >= maxRetries) {
finishFail();
return;
}
const int delayMs = static_cast<int>(baseWaitTime * std::pow(2.0, state->retryCount[i] - 1));
auto timer = new QTimer(state->self);
state->retryTimers[i] = timer;
timer->setSingleShot(true);
QObject::connect(timer,
&QTimer::timeout,
[state, i, timer]() mutable
{
if (state->finished) return;
timer->deleteLater();
state->retryTimers[i] = nullptr;
state->tryTranslateIndex(i);
});
timer->start(delayMs);
};
const auto r = state->self->startSingleTranslation(singleArgs);
state->replies[i] = r;
if (!r && !state->finished) {
singleArgs.onFail();
}
};
state->pump = [state, maxConcurrent]() mutable
{
if (state->finished) return;
while (!state->finished && state->inProgress < maxConcurrent && state->nextIndex < state->total) {
const int i = state->nextIndex++;
state->inProgress++;
state->tryTranslateIndex(i);
}
};
state->pump();
return [state, finishFail]() mutable
{
finishFail();
};
}
}
@@ -0,0 +1,121 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
#include <QtCore/QObject>
#include <QtCore/QPointer>
#include <QtCore/QSet>
#include <QtCore/QString>
#include <QtNetwork/QNetworkReply>
#include <functional>
#include "apiwrap.h"
namespace Main {
class Session;
}
namespace Ayu::Translator {
using CallbackSuccess = std::function<void(const std::vector<TextWithEntities> &)>;
using CallbackFail = std::function<void()>;
using CallbackCancel = std::function<void()>;
using MultiThreadCallbackSuccess = std::function<void(const TextWithEntities &)>;
QString randomDesktopUserAgent();
bool shouldWrapInHtml();
QString parseJsonPath(const QByteArray &body, const QString &jsonPath, bool *ok);
struct PassedData
{
MTPflags<MTPmessages_translateText::Flags> flags;
MTPInputPeer peer;
MTPVector<MTPint> idList;
MTPVector<MTPTextWithEntities> text;
MTPstring toLang;
};
struct ParsedData
{
std::vector<TextWithEntities> texts;
QString fromLang;
QString toLang;
};
struct StartTranslationArgs
{
Main::Session *session;
PassedData requestData;
ParsedData parsedData;
CallbackSuccess onSuccess;
CallbackFail onFail;
};
struct ParsedDataSingle
{
TextWithEntities text;
QString fromLang;
QString toLang;
};
struct MultiThreadArgs
{
ParsedDataSingle parsedData;
MultiThreadCallbackSuccess onSuccess;
CallbackFail onFail;
};
class BaseTranslator : public QObject
{
Q_OBJECT
public:
explicit BaseTranslator(QObject *parent = nullptr)
: QObject(parent) {
}
~BaseTranslator() override = default;
[[nodiscard]] virtual QSet<QString> supportedLanguages() const { return {}; }
[[nodiscard]] virtual CallbackCancel startTranslation(
const StartTranslationArgs &args
) = 0;
};
class MultiThreadTranslator : public BaseTranslator
{
Q_OBJECT
public:
explicit MultiThreadTranslator(QObject *parent = nullptr)
: BaseTranslator(parent) {
}
~MultiThreadTranslator() override = default;
[[nodiscard]] virtual int getConcurrencyLimit() const { return 1; }
[[nodiscard]] virtual int getMaxRetries() const { return 3; }
[[nodiscard]] virtual int getBaseWaitTimeMs() const { return 1000; }
[[nodiscard]] CallbackCancel startTranslation(
const StartTranslationArgs &args
) override;
[[nodiscard]] virtual QPointer<QNetworkReply> startSingleTranslation(
const MultiThreadArgs &args
) = 0;
};
}
@@ -0,0 +1,109 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "google.h"
#include <memory>
#include <QtCore/QJsonArray>
#include <QtCore/QPointer>
#include <QtCore/QTimer>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
#include <QtCore/QUrl>
#include <QtCore/QUrlQuery>
#include "ayu/features/translator/html_parser.h"
namespace Ayu::Translator {
GoogleTranslator &GoogleTranslator::instance() {
static GoogleTranslator inst;
return inst;
}
GoogleTranslator::GoogleTranslator(QObject *parent)
: MultiThreadTranslator(parent) {
}
QPointer<QNetworkReply> GoogleTranslator::startSingleTranslation(
const MultiThreadArgs &args
) {
const auto &text = args.parsedData.text;
const auto &fromLang = args.parsedData.fromLang;
const auto &toLang = args.parsedData.toLang;
const auto onSuccess = args.onSuccess;
const auto onFail = args.onFail;
if (text.empty() || toLang.isEmpty()) {
if (onFail) onFail();
return nullptr;
}
const auto from = fromLang.trimmed().isEmpty() ? QStringLiteral("auto") : fromLang.trimmed();
const auto to = toLang.trimmed();
QUrl url(QStringLiteral("https://translate.googleapis.com/translate_a/single"));
QUrlQuery query;
query.addQueryItem(QStringLiteral("dj"), QStringLiteral("1"));
query.addQueryItem(QStringLiteral("q"), shouldWrapInHtml() ? Html::entitiesToHtml(text) : text.text);
query.addQueryItem(QStringLiteral("sl"), from);
query.addQueryItem(QStringLiteral("tl"), to);
query.addQueryItem(QStringLiteral("ie"), QStringLiteral("UTF-8"));
query.addQueryItem(QStringLiteral("oe"), QStringLiteral("UTF-8"));
query.addQueryItem(QStringLiteral("client"), QStringLiteral("at"));
query.addQueryItem(QStringLiteral("dt"), QStringLiteral("t"));
query.addQueryItem(QStringLiteral("otf"), QStringLiteral("2"));
url.setQuery(query);
QNetworkRequest req(url);
const auto userAgent = randomDesktopUserAgent();
req.setHeader(QNetworkRequest::UserAgentHeader, userAgent);
QPointer<QNetworkReply> reply = _nam.get(req);
auto timer = new QTimer(reply);
timer->setSingleShot(true);
timer->setInterval(15000);
QObject::connect(timer,
&QTimer::timeout,
reply,
[reply]
{
if (!reply) return;
if (reply->isRunning()) reply->abort();
});
timer->start();
QObject::connect(reply,
&QNetworkReply::finished,
reply,
[reply, onSuccess = onSuccess, onFail = onFail, timer]
{
if (!reply) return;
timer->stop();
const auto guard = std::unique_ptr<QNetworkReply, void(*)(QNetworkReply *)>(
reply,
[](QNetworkReply *r) { r->deleteLater(); });
if (reply->error() != QNetworkReply::NoError) {
if (onFail) onFail();
return;
}
const auto body = reply->readAll();
bool ok = false;
const auto textOut = parseJsonPath(body, QStringLiteral("sentences"), &ok);
if (!ok) {
if (onFail) onFail();
return;
}
if (onSuccess) onSuccess(shouldWrapInHtml()
? Html::htmlToEntities(textOut)
: TextWithEntities{textOut});
});
return reply;
}
}
@@ -0,0 +1,38 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
#include <QtCore/QObject>
#include <QtCore/QPointer>
#include <QtCore/QSet>
#include <QtCore/QString>
#include "./base.h"
namespace Ayu::Translator {
class GoogleTranslator final : public MultiThreadTranslator
{
Q_OBJECT
public:
static GoogleTranslator &instance();
// all languages
[[nodiscard]] QSet<QString> supportedLanguages() const override { return {}; }
[[nodiscard]] QPointer<QNetworkReply> startSingleTranslation(
const MultiThreadArgs &args
) override;
private:
explicit GoogleTranslator(QObject *parent = nullptr);
QNetworkAccessManager _nam;
};
}
@@ -0,0 +1,65 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "telegram.h"
#include <QtNetwork/QNetworkReply>
#include "api/api_text_entities.h"
#include "main/main_session.h"
namespace Ayu::Translator {
TelegramTranslator &TelegramTranslator::instance() {
static TelegramTranslator inst;
return inst;
}
TelegramTranslator::TelegramTranslator(QObject *parent)
: BaseTranslator(parent) {
}
CallbackCancel TelegramTranslator::startTranslation(
const StartTranslationArgs &args
) {
const auto requestId = args.session->api().request(MTPmessages_TranslateText(
args.requestData.flags,
args.requestData.peer,
args.requestData.idList,
args.requestData.text,
args.requestData.toLang
)).done([=](const MTPmessages_TranslatedText &result)
{
const auto &data = result.data();
const auto &list = data.vresult().v;
if (list.isEmpty()) {
args.onFail();
} else {
auto vec = std::vector<TextWithEntities>();
vec.reserve(list.size());
for (const auto &item : list) {
const auto &d = item.data();
vec.push_back(TextWithEntities{
.text = qs(d.vtext()),
.entities = Api::EntitiesFromMTP(
args.session,
d.ventities().v)
});
}
args.onSuccess(vec);
}
}).fail([=](const MTP::Error &)
{
args.onFail();
}).send();
return [=]
{
args.session->api().request(requestId).cancel();
};
}
}
@@ -0,0 +1,34 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
#include <QtCore/QObject>
#include <QtCore/QSet>
#include <QtCore/QString>
#include "./base.h"
namespace Ayu::Translator {
class TelegramTranslator final : public BaseTranslator
{
Q_OBJECT
public:
static TelegramTranslator &instance();
[[nodiscard]] QSet<QString> supportedLanguages() const override { return {}; }
[[nodiscard]] CallbackCancel startTranslation(
const StartTranslationArgs &args
) override;
private:
explicit TelegramTranslator(QObject *parent = nullptr);
};
}
@@ -0,0 +1,140 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "yandex.h"
#include <memory>
#include <QtCore/QPointer>
#include <QtCore/QTimer>
#include <QtCore/QUrl>
#include <QtCore/QUrlQuery>
#include <QtCore/QUuid>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
#include "ayu/features/translator/html_parser.h"
namespace Ayu::Translator {
YandexTranslator &YandexTranslator::instance() {
static YandexTranslator inst;
return inst;
}
YandexTranslator::YandexTranslator(QObject *parent)
: MultiThreadTranslator(parent)
, _uuid(QUuid::createUuid().toString(QUuid::WithoutBraces).replace("-", "")) {
}
QSet<QString> YandexTranslator::supportedLanguages() const {
static const QSet<QString> languages = {
QStringLiteral("az"), QStringLiteral("sq"), QStringLiteral("am"), QStringLiteral("en"),
QStringLiteral("ar"), QStringLiteral("hy"), QStringLiteral("af"), QStringLiteral("eu"),
QStringLiteral("ba"), QStringLiteral("be"), QStringLiteral("bn"), QStringLiteral("my"),
QStringLiteral("bg"), QStringLiteral("bs"), QStringLiteral("cv"), QStringLiteral("cy"),
QStringLiteral("hu"), QStringLiteral("vi"), QStringLiteral("ht"), QStringLiteral("gl"),
QStringLiteral("nl"), QStringLiteral("mrj"), QStringLiteral("el"), QStringLiteral("ka"),
QStringLiteral("gu"), QStringLiteral("da"), QStringLiteral("he"), QStringLiteral("yi"),
QStringLiteral("id"), QStringLiteral("ga"), QStringLiteral("it"), QStringLiteral("is"),
QStringLiteral("es"), QStringLiteral("kk"), QStringLiteral("kn"), QStringLiteral("ca"),
QStringLiteral("ky"), QStringLiteral("zh"), QStringLiteral("ko"), QStringLiteral("xh"),
QStringLiteral("km"), QStringLiteral("lo"), QStringLiteral("la"), QStringLiteral("lv"),
QStringLiteral("lt"), QStringLiteral("lb"), QStringLiteral("mg"), QStringLiteral("ms"),
QStringLiteral("ml"), QStringLiteral("mt"), QStringLiteral("mk"), QStringLiteral("mi"),
QStringLiteral("mr"), QStringLiteral("mhr"), QStringLiteral("mn"), QStringLiteral("de"),
QStringLiteral("ne"), QStringLiteral("no"), QStringLiteral("pa"), QStringLiteral("pap"),
QStringLiteral("fa"), QStringLiteral("pl"), QStringLiteral("pt"), QStringLiteral("ro"),
QStringLiteral("ru"), QStringLiteral("ceb"), QStringLiteral("sr"), QStringLiteral("si"),
QStringLiteral("sk"), QStringLiteral("sl"), QStringLiteral("sw"), QStringLiteral("su"),
QStringLiteral("tg"), QStringLiteral("th"), QStringLiteral("tl"), QStringLiteral("ta"),
QStringLiteral("tt"), QStringLiteral("te"), QStringLiteral("tr"), QStringLiteral("udm"),
QStringLiteral("uz"), QStringLiteral("uk"), QStringLiteral("ur"), QStringLiteral("fi"),
QStringLiteral("fr"), QStringLiteral("hi"), QStringLiteral("hr"), QStringLiteral("cs"),
QStringLiteral("sv"), QStringLiteral("gd"), QStringLiteral("et"), QStringLiteral("eo"),
QStringLiteral("jv"), QStringLiteral("ja")
};
return languages;
}
QPointer<QNetworkReply> YandexTranslator::startSingleTranslation(
const MultiThreadArgs &args
) {
const auto &text = args.parsedData.text;
// const auto &fromLang = args.parsedData.fromLang;
const auto &toLang = args.parsedData.toLang;
const auto onSuccess = args.onSuccess;
const auto onFail = args.onFail;
if (text.empty() || toLang.isEmpty()) {
if (onFail) onFail();
return nullptr;
}
const auto to = toLang.trimmed();
QUrl url(QStringLiteral("https://translate.yandex.net/api/v1/tr.json/translate"));
QUrlQuery query;
query.addQueryItem(QStringLiteral("srv"), QStringLiteral("android"));
query.addQueryItem(QStringLiteral("id"), _uuid + QStringLiteral("-0-0"));
url.setQuery(query);
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader,
QStringLiteral("ru.yandex.translate/21.15.4.21402814 (Xiaomi Redmi K20 Pro; Android 11)"));
req.setHeader(QNetworkRequest::ContentTypeHeader,
QStringLiteral("application/x-www-form-urlencoded"));
QUrlQuery postData;
postData.addQueryItem(QStringLiteral("lang"), to);
postData.addQueryItem(QStringLiteral("text"), shouldWrapInHtml() ? Html::entitiesToHtml(text) : text.text);
const auto postDataEncoded = postData.toString(QUrl::FullyEncoded).toUtf8();
QPointer<QNetworkReply> reply = _nam.post(req, postDataEncoded);
auto timer = new QTimer(reply);
timer->setSingleShot(true);
timer->setInterval(15000);
QObject::connect(timer,
&QTimer::timeout,
reply,
[reply]
{
if (!reply) return;
if (reply->isRunning()) reply->abort();
});
timer->start();
QObject::connect(reply,
&QNetworkReply::finished,
[reply, onSuccess = onSuccess, onFail = onFail, timer]
{
if (!reply) return;
timer->stop();
const auto guard = std::unique_ptr<QNetworkReply, void(*)(QNetworkReply *)>(
reply,
[](QNetworkReply *r) { r->deleteLater(); });
if (reply->error() != QNetworkReply::NoError) {
if (onFail) onFail();
return;
}
const auto body = reply->readAll();
bool ok = false;
const auto translatedText = parseJsonPath(body, QStringLiteral("text"), &ok);
if (!ok) {
if (onFail) onFail();
return;
}
if (onSuccess) onSuccess(shouldWrapInHtml()
? Html::htmlToEntities(translatedText)
: TextWithEntities{translatedText});
});
return reply;
}
}
@@ -0,0 +1,38 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
#include <QtCore/QObject>
#include <QtCore/QPointer>
#include <QtCore/QSet>
#include <QtCore/QString>
#include "./base.h"
namespace Ayu::Translator {
class YandexTranslator final : public MultiThreadTranslator
{
Q_OBJECT
public:
static YandexTranslator &instance();
[[nodiscard]] QSet<QString> supportedLanguages() const override;
[[nodiscard]] QPointer<QNetworkReply> startSingleTranslation(
const MultiThreadArgs &args
) override;
private:
explicit YandexTranslator(QObject *parent = nullptr);
QNetworkAccessManager _nam;
QString _uuid;
};
}
+3 -1
View File
@@ -12,7 +12,7 @@ using "ui/colors.palette";
using "ui/widgets/widgets.style";
using "info/info.style";
/* Color Picker */
/* Icon Picker */
cpPadding: 14px;
cpSelectedPadding: 2px;
cpSelectedRounding: 12px;
@@ -69,3 +69,5 @@ exteraBadgeToast: Toast(defaultToast) {
icon: icon {{ "ayu/extera_badge", toastFg }};
iconPosition: point(13px, 13px);
}
supportLogoSize: 96px;
@@ -0,0 +1,244 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "ayu/ui/boxes/donate_info_box.h"
#include <QSvgRenderer>
#include "lang_auto.h"
#include "ayu/utils/rc_manager.h"
#include "core/ui_integration.h"
#include "data/data_session.h"
#include "data/stickers/data_custom_emoji.h"
#include "info/channel_statistics/earn/earn_icons.h"
#include "lang/lang_text_entity.h"
#include "info/profile/info_profile_icon.h"
#include "main/main_session.h"
#include "styles/style_ayu_styles.h"
#include "styles/style_boxes.h"
#include "styles/style_channel_earn.h"
#include "styles/style_giveaway.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_premium.h"
#include "styles/style_settings.h"
#include "styles/style_widgets.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/rp_widget.h"
#include "ui/vertical_list.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "window/window_session_controller.h"
namespace Ui {
namespace {
QImage MakeSupportLogo() {
const auto s = Size(st::supportLogoSize);
auto svg = QSvgRenderer(QString(":/gui/icons/ayu/donates/support_logo.svg"));
auto image = QImage(
s * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(style::DevicePixelRatio());
image.fill(Qt::transparent);
{
auto p = QPainter(&image);
svg.render(&p, Rect(s));
}
return image;
}
object_ptr<Ui::RpWidget> CreateTopLogoWidget(
not_null<Ui::RpWidget*> parent) {
auto w = object_ptr<Ui::RpWidget>(parent);
const auto raw = w.data();
const auto logo = MakeSupportLogo();
raw->paintRequest(
) | rpl::start_with_next(
[=](QRect)
{
QPainter p(raw);
PainterHighQualityEnabler hq(p);
const auto original = logo.size() / style::DevicePixelRatio();
const auto maxWidth = raw->width() - rect::m::sum::h(st::boxRowPadding);
const auto maxHeight = raw->height();
if (original.isEmpty() || maxWidth <= 0 || maxHeight <= 0) {
return;
}
const auto scale = std::min(
double(maxWidth) / double(original.width()),
double(maxHeight) / double(original.height()));
const auto target = QSize(
int(original.width() * scale),
int(original.height() * scale));
const auto x = (raw->width() - target.width()) / 2;
const auto y = (raw->height() - target.height()) / 2;
const auto rect = QRect(QPoint(x, y), target);
p.drawImage(rect, logo);
},
raw->lifetime());
return w;
}
object_ptr<Ui::RpWidget> InfoRow(
not_null<Ui::RpWidget*> parent,
not_null<Main::Session*> session,
const QString &title,
const TextWithEntities &text,
not_null<const style::icon*> icon) {
auto row = object_ptr<Ui::VerticalLayout>(parent);
const auto raw = row.data();
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
rpl::single(title) | Ui::Text::ToBold(),
st::defaultFlatLabel),
st::settingsPremiumRowTitlePadding);
const auto label = raw->add(
object_ptr<Ui::FlatLabel>(
raw,
st::boxDividerLabel),
st::settingsPremiumRowAboutPadding);
label->setMarkedText(
text,
Core::TextContext({
.session = std::move(session),
})
);
object_ptr<Info::Profile::FloatingIcon>(
raw,
*icon,
st::starrefInfoIconPosition);
return row;
}
} // namespace
void FillDonateInfoBox(not_null<Ui::GenericBox*> box, not_null<Window::SessionController*> controller) {
// box->setStyle(st::starrefFooterBox);
box->setStyle(st::giveawayGiftCodeBox);
box->setNoContentMargin(true);
box->setWidth(int(st::aboutWidth * 1.1));
box->verticalLayout()->resizeToWidth(box->width());
box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); });
Ui::AddSkip(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
const auto logoWidget = box->verticalLayout()->add(
CreateTopLogoWidget(box->verticalLayout()));
logoWidget->resize(st::supportLogoSize, st::supportLogoSize);
Ui::AddSkip(box->verticalLayout());
box->verticalLayout()->add(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
object_ptr<Ui::FlatLabel>(
box->verticalLayout(),
tr::ayu_SupportBoxHeader()
| Ui::Text::ToBold(),
st::boxTitle)),
st::boxRowPadding);
box->verticalLayout()->add(
object_ptr<Ui::FlatLabel>(
box->verticalLayout(),
tr::ayu_SupportBoxInfo(),
st::starrefCenteredText),
st::boxRowPadding);
Ui::AddSkip(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
const auto tonSymbol = Ui::Text::SingleCustomEmoji(
controller->session().data().customEmojiManager().registerInternalEmoji(
Ui::Earn::IconCurrencyColored(
st::boxDividerLabel.style.font,
st::boxDividerLabel.textFg->c),
st::channelEarnCurrencyLearnMargins));
const auto dollarAmount = RCManager::getInstance().donateAmountUsd().prepend("$");
const auto tonAmount = RCManager::getInstance().donateAmountTon();
const auto rubleAmount = RCManager::getInstance().donateAmountRub().append("");
const auto innerText = TextWithEntities{}.append(tonSymbol).append(tonAmount).append(", ").append(rubleAmount);
const auto str = tr::ayu_SupportBoxMakeDonationInfo(
tr::now,
lt_amount1,
TextWithEntities{dollarAmount},
lt_amount2,
innerText,
Ui::Text::RichLangValue
);
box->verticalLayout()->add(InfoRow(
box->verticalLayout(),
&controller->session(),
tr::ayu_SupportBoxMakeDonationHeader(tr::now),
str,
&st::menuIconEarn));
Ui::AddSkip(box->verticalLayout());
const auto username = RCManager::getInstance().donateUsername();
auto usernameTrimmed = username;
if (usernameTrimmed.startsWith('@')) {
usernameTrimmed.remove(0, 1);
}
const TextWithEntities proofText = tr::ayu_SupportBoxSendProofInfo(
tr::now,
lt_item,
Ui::Text::Link(username, controller->session().createInternalLinkFull(usernameTrimmed)),
Ui::Text::RichLangValue);
box->verticalLayout()->add(InfoRow(
box->verticalLayout(),
&controller->session(),
tr::ayu_SupportBoxSendProofHeader(tr::now),
proofText,
&st::menuIconPhoto));
Ui::AddSkip(box->verticalLayout());
box->verticalLayout()->add(InfoRow(
box->verticalLayout(),
&controller->session(),
tr::ayu_SupportBoxReceiveBadgeHeader(tr::now),
TextWithEntities{
tr::ayu_SupportBoxReceiveBadgeInfo(tr::now)
},
&st::menuIconStarRefShare));
const auto closeButton = box->addButton(tr::lng_close(), [=] { box->closeBox(); });
const auto buttonWidth = box->width()
- rect::m::sum::h(st::starrefFooterBox.buttonPadding);
closeButton->widthValue() | rpl::filter([=]
{
return (closeButton->widthNoMargins() != buttonWidth);
}) | rpl::start_with_next([=]
{
closeButton->resizeToWidth(buttonWidth);
},
closeButton->lifetime());
}
} // namespace Ui
@@ -0,0 +1,19 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
namespace Window {
class SessionController;
}
namespace Ui {
class GenericBox;
void FillDonateInfoBox(not_null<Ui::GenericBox*> box, not_null<Window::SessionController*> controller);
} // namespace Ui
@@ -0,0 +1,164 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "ayu/ui/boxes/donate_qr_box.h"
#include "qr/qr_generate.h"
#include "styles/style_boxes.h"
#include "styles/style_intro.h"
#include "styles/style_layers.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/rp_widget.h"
#include "ui/vertical_list.h"
#include "ui/controls/invite_link_label.h"
#include "ui/layers/generic_box.h"
#include "ui/widgets/buttons.h"
#include <QtGui/QClipboard>
#include <QtGui/QGuiApplication>
#include <QtSvg/QSvgRenderer>
#include "lang_auto.h"
#include "styles/style_giveaway.h"
#include "ui/toast/toast.h"
namespace Ui {
namespace {
[[nodiscard]] QImage MakeQrWithIcon(
const QByteArray &payload,
const QString &iconPath,
int pixel,
int max) {
Expects(!payload.isEmpty());
const auto data = Qr::Encode(payload, Qr::Redundancy::Default);
Expects(data.size > 0);
if (max > 0 && data.size * pixel > max) {
pixel = std::max(max / data.size, 1);
}
auto qr = Qr::Generate(
data,
pixel * style::DevicePixelRatio(),
Qt::black,
Qt::white);
{
QPainter p(&qr);
PainterHighQualityEnabler hq(p);
const auto size = qr.rect().size();
constexpr auto kCenterRatio = 0.20;
const auto centerRect = Rect(size)
- Margins((size.width() - (size.width() * kCenterRatio)) / 2);
p.setPen(Qt::NoPen);
p.setBrush(Qt::white);
p.drawEllipse(centerRect);
QSvgRenderer svg(iconPath);
if (svg.isValid()) {
svg.render(&p, centerRect);
}
}
return qr;
}
} // namespace
void FillDonateQrBox(
not_null<Ui::GenericBox*> box,
const QString &address,
const QString &iconResourcePath) {
box->setStyle(st::giveawayGiftCodeBox);
box->setNoContentMargin(true);
box->setWidth(int(st::aboutWidth * 1.25));
box->setTitle(tr::lng_group_invite_context_qr());
box->verticalLayout()->resizeToWidth(box->width());
Ui::AddSkip(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
const auto qrWidget = box->verticalLayout()->add(
object_ptr<Ui::RpWidget>(box->verticalLayout()));
struct State {
QImage qrImage;
int qrMaxSize = 0;
};
const auto state = qrWidget->lifetime().make_state<State>();
const auto recompute = [=] {
const auto qrMaxSize = int(st::aboutWidth * 1.25) - st::boxRowPadding.left() - st::boxRowPadding.right();
state->qrMaxSize = qrMaxSize;
const auto remainder = qrMaxSize % st::introQrPixel;
const auto downTo = remainder ? (qrMaxSize - remainder) : qrMaxSize;
state->qrImage = MakeQrWithIcon(
address.toUtf8(),
iconResourcePath,
st::introQrPixel,
downTo).scaled(
Size(qrMaxSize * style::DevicePixelRatio()),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
qrWidget->resize(
state->qrMaxSize,
state->qrMaxSize);
};
recompute();
qrWidget->paintRequest(
) | rpl::start_with_next([=](QRect) {
QPainter p(qrWidget);
PainterHighQualityEnabler hq(p);
const auto size = state->qrImage.size() / style::DevicePixelRatio();
const auto rect = Rect(
(qrWidget->width() - size.width()) / 2,
(qrWidget->height() - size.height()) / 2,
size);
QPainterPath path;
path.addRoundedRect(rect, st::roundRadiusLarge, st::roundRadiusLarge);
p.setPen(Qt::NoPen);
p.setBrush(Qt::white);
p.drawPath(path);
p.setClipPath(path);
const auto padding = st::boxRowPadding.left();
const auto innerRect = rect - Margins(padding);
p.drawImage(innerRect, state->qrImage);
}, qrWidget->lifetime());
Ui::AddSkip(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
const auto label = box->lifetime().make_state<Ui::InviteLinkLabel>(
box->verticalLayout(),
rpl::single(address),
Fn<base::unique_qptr<Ui::PopupMenu>()>());
box->verticalLayout()->add(label->take(), st::boxRowPadding);
const auto copyButton = box->addButton(tr::lng_chat_link_copy(), [=] {
QGuiApplication::clipboard()->setText(address);
Ui::Toast::Show(tr::lng_text_copied(tr::now));
});
const auto buttonWidth = box->width()
- rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding);
copyButton->widthValue() | rpl::filter([=] {
return (copyButton->widthNoMargins() != buttonWidth);
}) | rpl::start_with_next([=] {
copyButton->resizeToWidth(buttonWidth);
}, copyButton->lifetime());
box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); });
}
} // namespace Ui
@@ -0,0 +1,18 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
namespace Ui {
class GenericBox;
void FillDonateQrBox(
not_null<Ui::GenericBox*> box,
const QString &address,
const QString &iconResourcePath);
} // namespace Ui
@@ -58,7 +58,7 @@ void drawIcon(QPainter &p, const QImage &icon, int xOffset, int yOffset, float s
);
p.restore();
auto rect = QRect(
const auto rect = QRect(
xOffset + st::cpImagePadding,
yOffset + st::cpImagePadding,
st::cpIconSize,
@@ -87,6 +87,7 @@ IconPicker::IconPicker(QWidget *parent)
IconPicker::~IconPicker() {
cachedIcons.clear();
}
void IconPicker::paintEvent(QPaintEvent *e) {
Painter p(this);
PainterHighQualityEnabler hq(p);
@@ -0,0 +1,23 @@
/*
* This is the source code of AyuGram for Desktop.
*
* We do not and cannot prevent the use of our code,
* but be respectful and credit the original author.
*
* Copyright @Radolyn, 2025
*/
using "ui/basic.style";
using "ui/layers/layers.style";
using "ui/widgets/widgets.style";
centeredBoxLabelStyle: TextStyle(boxLabelStyle) {
font: font(13px);
lineHeight: 0px;
}
centeredBoxLabel: FlatLabel(defaultFlatLabel) {
minWidth: 256px;
align: align(center);
style: centeredBoxLabelStyle;
}
@@ -0,0 +1,543 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "settings_appearance.h"
#include "lang_auto.h"
#include "ayu/ayu_settings.h"
#include "ayu/ui/boxes/font_selector.h"
#include "ayu/ui/components/icon_picker.h"
#include "inline_bots/bot_attach_web_view.h"
#include "main/main_session.h"
#include "settings/settings_common.h"
#include "styles/style_ayu_icons.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
namespace Settings {
namespace {
bool HasDrawerBots(not_null<Window::SessionController*> controller) {
// todo: maybe iterate through all accounts
const auto bots = &controller->session().attachWebView();
for (const auto &bot : bots->attachBots()) {
if (!bot.inMainMenu || !bot.media) {
continue;
}
return true;
}
return false;
}
}
rpl::producer<QString> AyuAppearance::title() {
return tr::ayu_CategoryAppearance();
}
AyuAppearance::AyuAppearance(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Section(parent) {
setupContent(controller);
}
void SetupAppIcon(not_null<Ui::VerticalLayout*> container) {
AddSubsectionTitle(container, tr::ayu_AppIconHeader());
container->add(
object_ptr<IconPicker>(container),
st::settingsCheckboxPadding);
#ifdef Q_OS_WIN
auto *settings = &AyuSettings::getInstance();
AddDivider(container);
AddSkip(container);
AddButtonWithIcon(
container,
tr::ayu_HideNotificationBadge(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->hideNotificationBadge)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->hideNotificationBadge);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_hideNotificationBadge(enabled);
AyuSettings::save();
},
container->lifetime());
AddSkip(container);
AddDividerText(container, tr::ayu_HideNotificationBadgeDescription());
AddSkip(container);
#endif
}
void SetupAppearance(not_null<Ui::VerticalLayout*> container, not_null<Window::SessionController*> controller) {
auto *settings = &AyuSettings::getInstance();
AddSubsectionTitle(container, tr::ayu_CategoryAppearance());
AddButtonWithIcon(
container,
tr::ayu_MaterialSwitches(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->materialSwitches)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->materialSwitches);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_materialSwitches(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_RemoveMessageTail(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->removeMessageTail)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->removeMessageTail);
}) | start_with_next(
[=](bool enabled)
{
AyuSettings::set_removeMessageTail(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_DisableCustomBackgrounds(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->disableCustomBackgrounds)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->disableCustomBackgrounds);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_disableCustomBackgrounds(enabled);
AyuSettings::save();
},
container->lifetime());
const auto monoButton = AddButtonWithLabel(
container,
tr::ayu_MonospaceFont(),
rpl::single(
settings->monoFont.isEmpty() ? tr::ayu_FontDefault(tr::now) : settings->monoFont
),
st::settingsButtonNoIcon);
const auto monoGuard = Ui::CreateChild<base::binary_guard>(monoButton.get());
monoButton->addClickHandler(
[=]
{
*monoGuard = AyuUi::FontSelectorBox::Show(
controller,
[=](QString font)
{
AyuSettings::set_monoFont(std::move(font));
AyuSettings::save();
});
});
AddSkip(container);
AddDivider(container);
AddSkip(container);
}
void SetupChatFolders(not_null<Ui::VerticalLayout*> container) {
auto *settings = &AyuSettings::getInstance();
AddSubsectionTitle(container, tr::ayu_ChatFoldersHeader());
AddButtonWithIcon(
container,
tr::ayu_HideNotificationCounters(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->hideNotificationCounters)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->hideNotificationCounters);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_hideNotificationCounters(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_HideAllChats(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->hideAllChatsFolder)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->hideAllChatsFolder);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_hideAllChatsFolder(enabled);
AyuSettings::save();
},
container->lifetime());
AddSkip(container);
AddDivider(container);
AddSkip(container);
}
void SetupDrawerElements(not_null<Ui::VerticalLayout*> container, not_null<Window::SessionController*> controller) {
auto *settings = &AyuSettings::getInstance();
AddSubsectionTitle(container, tr::ayu_DrawerElementsHeader());
AddButtonWithIcon(
container,
tr::lng_menu_my_profile(),
st::settingsButton,
{&st::menuIconProfile}
)->toggleOn(
rpl::single(settings->showMyProfileInDrawer)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showMyProfileInDrawer);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showMyProfileInDrawer(enabled);
AyuSettings::save();
},
container->lifetime());
if (HasDrawerBots(controller)) {
AddButtonWithIcon(
container,
tr::lng_filters_type_bots(),
st::settingsButton,
{&st::menuIconBot}
)->toggleOn(
rpl::single(settings->showBotsInDrawer)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showBotsInDrawer);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showBotsInDrawer(enabled);
AyuSettings::save();
},
container->lifetime());
}
AddButtonWithIcon(
container,
tr::lng_create_group_title(),
st::settingsButton,
{&st::menuIconGroups}
)->toggleOn(
rpl::single(settings->showNewGroupInDrawer)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showNewGroupInDrawer);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showNewGroupInDrawer(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::lng_create_channel_title(),
st::settingsButton,
{&st::menuIconChannel}
)->toggleOn(
rpl::single(settings->showNewChannelInDrawer)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showNewChannelInDrawer);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showNewChannelInDrawer(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::lng_menu_contacts(),
st::settingsButton,
{&st::menuIconUserShow}
)->toggleOn(
rpl::single(settings->showContactsInDrawer)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showContactsInDrawer);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showContactsInDrawer(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::lng_menu_calls(),
st::settingsButton,
{&st::menuIconPhone}
)->toggleOn(
rpl::single(settings->showCallsInDrawer)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showCallsInDrawer);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showCallsInDrawer(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::lng_saved_messages(),
st::settingsButton,
{&st::menuIconSavedMessages}
)->toggleOn(
rpl::single(settings->showSavedMessagesInDrawer)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showSavedMessagesInDrawer);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showSavedMessagesInDrawer(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_LReadMessages(),
st::settingsButton,
{&st::ayuLReadMenuIcon}
)->toggleOn(
rpl::single(settings->showLReadToggleInDrawer)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showLReadToggleInDrawer);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showLReadToggleInDrawer(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_SReadMessages(),
st::settingsButton,
{&st::ayuSReadMenuIcon}
)->toggleOn(
rpl::single(settings->showSReadToggleInDrawer)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showSReadToggleInDrawer);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showSReadToggleInDrawer(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::lng_menu_night_mode(),
st::settingsButton,
{&st::menuIconNightMode}
)->toggleOn(
rpl::single(settings->showNightModeToggleInDrawer)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showNightModeToggleInDrawer);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showNightModeToggleInDrawer(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_GhostModeToggle(),
st::settingsButton,
{&st::ayuGhostIcon}
)->toggleOn(
rpl::single(settings->showGhostToggleInDrawer)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showGhostToggleInDrawer);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showGhostToggleInDrawer(enabled);
AyuSettings::save();
},
container->lifetime());
#ifdef WIN32
AddButtonWithIcon(
container,
tr::ayu_StreamerModeToggle(),
st::settingsButton,
{&st::ayuStreamerModeMenuIcon}
)->toggleOn(
rpl::single(settings->showStreamerToggleInDrawer)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showStreamerToggleInDrawer);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showStreamerToggleInDrawer(enabled);
AyuSettings::save();
},
container->lifetime());
#endif
AddSkip(container);
}
void SetupTrayElements(not_null<Ui::VerticalLayout*> container) {
auto *settings = &AyuSettings::getInstance();
AddSubsectionTitle(container, tr::ayu_TrayElementsHeader());
AddButtonWithIcon(
container,
tr::ayu_EnableGhostModeTray(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->showGhostToggleInTray)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showGhostToggleInTray);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showGhostToggleInTray(enabled);
AyuSettings::save();
},
container->lifetime());
#ifdef WIN32
AddButtonWithIcon(
container,
tr::ayu_EnableStreamerModeTray(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->showStreamerToggleInTray)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showStreamerToggleInTray);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showStreamerToggleInTray(enabled);
AyuSettings::save();
},
container->lifetime());
#endif
AddSkip(container);
AddDivider(container);
AddSkip(container);
}
void AyuAppearance::setupContent(not_null<Window::SessionController*> controller) {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
AddSkip(content);
SetupAppIcon(content);
SetupAppearance(content, controller);
SetupChatFolders(content);
SetupTrayElements(content);
SetupDrawerElements(content, controller);
AddSkip(content);
ResizeFitChild(this, content);
}
} // namespace Settings
@@ -0,0 +1,29 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
#include "settings/settings_common.h"
#include "settings/settings_common_session.h"
namespace Window {
class SessionController;
} // namespace Window
namespace Settings {
class AyuAppearance : public Section<AyuAppearance>
{
public:
AyuAppearance(QWidget *parent, not_null<Window::SessionController*> controller);
[[nodiscard]] rpl::producer<QString> title() override;
private:
void setupContent(not_null<Window::SessionController*> controller);
};
} // namespace Settings
File diff suppressed because it is too large Load Diff
@@ -9,27 +9,21 @@
#include "settings/settings_common.h"
#include "settings/settings_common_session.h"
class BoxContent;
namespace Window {
class Controller;
class SessionController;
} // namespace Window
namespace Settings {
class Ayu : public Section<Ayu>
class AyuGhost : public Section<AyuGhost>
{
public:
Ayu(QWidget *parent, not_null<Window::SessionController*> controller);
AyuGhost(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);
};
} // namespace Settings
} // namespace Settings
@@ -0,0 +1,412 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "settings_ayu_utils.h"
#include "settings/settings_common.h"
#include "styles/style_info.h"
#include "styles/style_layers.h"
#include "styles/style_settings.h"
#include "ui/painter.h"
#include "ui/boxes/single_choice_box.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/continuous_sliders.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
class PainterHighQualityEnabler;
namespace Settings {
rpl::producer<QString> asBeta(rpl::producer<QString> text) {
return std::move(text) | rpl::map([=](const QString &val)
{
return val + " β";
});
}
not_null<Ui::RpWidget*> AddInnerToggle(not_null<Ui::VerticalLayout*> container,
const style::SettingsButton &st,
std::vector<not_null<Ui::AbstractCheckView*>> innerCheckViews,
not_null<Ui::SlideWrap<>*> wrap,
rpl::producer<QString> buttonLabel,
bool toggledWhenAll) {
const auto button = container->add(object_ptr<Ui::SettingsButton>(
container,
nullptr,
st::settingsButtonNoIcon));
const auto toggleButton = Ui::CreateChild<Ui::SettingsButton>(
container.get(),
nullptr,
st);
struct State final
{
State(const style::Toggle &st, Fn<void()> c)
: checkView(st, false, c) {
}
Ui::ToggleView checkView;
Ui::Animations::Simple animation;
rpl::event_stream<> anyChanges;
std::vector<not_null<Ui::AbstractCheckView*>> innerChecks;
};
const auto state = button->lifetime().make_state<State>(
st.toggle,
[=]
{
toggleButton->update();
});
state->innerChecks = std::move(innerCheckViews);
const auto countChecked = [=]
{
return ranges::count_if(
state->innerChecks,
[](const auto &v)
{
return v->checked();
});
};
for (const auto &innerCheck : state->innerChecks) {
innerCheck->checkedChanges(
) | rpl::to_empty | start_to_stream(
state->anyChanges,
button->lifetime());
}
const auto checkView = &state->checkView;
{
const auto separator = Ui::CreateChild<Ui::RpWidget>(container.get());
separator->paintRequest(
) | start_with_next([=, bg = st.textBgOver]
{
auto p = QPainter(separator);
p.fillRect(separator->rect(), bg);
},
separator->lifetime());
const auto separatorHeight = 2 * st.toggle.border
+ st.toggle.diameter;
button->geometryValue(
) | start_with_next([=](const QRect &r)
{
const auto w = st::rightsButtonToggleWidth;
constexpr auto kLineWidth = 1;
toggleButton->setGeometry(
r.x() + r.width() - w,
r.y(),
w,
r.height());
separator->setGeometry(
toggleButton->x() - kLineWidth,
r.y() + (r.height() - separatorHeight) / 2,
kLineWidth,
separatorHeight);
},
toggleButton->lifetime());
const auto checkWidget = Ui::CreateChild<Ui::RpWidget>(toggleButton);
checkWidget->resize(checkView->getSize());
checkWidget->paintRequest(
) | start_with_next([=]
{
auto p = QPainter(checkWidget);
checkView->paint(p, 0, 0, checkWidget->width());
},
checkWidget->lifetime());
toggleButton->sizeValue(
) | start_with_next([=](const QSize &s)
{
checkWidget->moveToRight(
st.toggleSkip,
(s.height() - checkWidget->height()) / 2);
},
toggleButton->lifetime());
}
const auto totalInnerChecks = state->innerChecks.size();
state->anyChanges.events_starting_with(
rpl::empty_value()
) | rpl::map(countChecked) | start_with_next([=](int count)
{
if (toggledWhenAll) {
checkView->setChecked(count == totalInnerChecks,
anim::type::normal);
} else {
checkView->setChecked(count != 0,
anim::type::normal);
}
},
toggleButton->lifetime());
checkView->setLocked(false);
checkView->finishAnimating();
const auto label = Ui::CreateChild<Ui::FlatLabel>(
button,
combine(
std::move(buttonLabel),
state->anyChanges.events_starting_with(
rpl::empty_value()
) | rpl::map(countChecked)
) | rpl::map([=](const QString &t, int checked)
{
auto count = Ui::Text::Bold(" "
+ QString::number(checked)
+ '/'
+ QString::number(totalInnerChecks));
return TextWithEntities::Simple(t).append(std::move(count));
}),
st::boxLabel);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto arrow = Ui::CreateChild<Ui::RpWidget>(button);
{
const auto &icon = st::permissionsExpandIcon;
arrow->resize(icon.size());
arrow->paintRequest(
) | start_with_next([=, &icon]
{
auto p = QPainter(arrow);
const auto center = QPointF(
icon.width() / 2.,
icon.height() / 2.);
const auto progress = state->animation.value(
wrap->toggled() ? 1. : 0.);
auto hq = std::optional<PainterHighQualityEnabler>();
if (progress > 0.) {
hq.emplace(p);
p.translate(center);
p.rotate(progress * 180.);
p.translate(-center);
}
icon.paint(p, 0, 0, arrow->width());
},
arrow->lifetime());
}
button->sizeValue(
) | start_with_next([=, &st](const QSize &s)
{
const auto labelLeft = st.padding.left();
const auto labelRight = s.width() - toggleButton->width();
label->resizeToWidth(labelRight - labelLeft - arrow->width());
label->moveToLeft(
labelLeft,
(s.height() - label->height()) / 2);
arrow->moveToLeft(
std::min(
labelLeft + label->naturalWidth(),
labelRight - arrow->width()),
(s.height() - arrow->height()) / 2);
},
button->lifetime());
wrap->toggledValue(
) | rpl::skip(1) | start_with_next([=](bool toggled)
{
state->animation.start(
[=]
{
arrow->update();
},
toggled ? 0. : 1.,
toggled ? 1. : 0.,
st::slideWrapDuration,
anim::easeOutCubic);
},
button->lifetime());
wrap->ease = anim::easeOutCubic;
button->clicks(
) | start_with_next([=]
{
wrap->toggle(!wrap->toggled(), anim::type::normal);
},
button->lifetime());
toggleButton->clicks(
) | start_with_next([=]
{
const auto checked = !checkView->checked();
for (const auto &innerCheck : state->innerChecks) {
innerCheck->setChecked(checked, anim::type::normal);
}
},
toggleButton->lifetime());
return button;
}
void AddCollapsibleToggle(not_null<Ui::VerticalLayout*> container,
rpl::producer<QString> title,
std::vector<NestedEntry> checkboxes,
bool toggledWhenAll) {
const auto addCheckbox = [&](
not_null<Ui::VerticalLayout*> verticalLayout,
const QString &label,
const bool isCheckedOrig)
{
const auto checkView = [&]() -> not_null<Ui::AbstractCheckView*>
{
const auto checkbox = verticalLayout->add(
object_ptr<Ui::Checkbox>(
verticalLayout,
label,
isCheckedOrig,
st::settingsCheckbox),
st::powerSavingButton.padding);
const auto button = Ui::CreateChild<Ui::RippleButton>(
verticalLayout.get(),
st::defaultRippleAnimation);
button->stackUnder(checkbox);
combine(
verticalLayout->widthValue(),
checkbox->geometryValue()
) | start_with_next([=](int w, const QRect &r)
{
button->setGeometry(0, r.y(), w, r.height());
},
button->lifetime());
checkbox->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto checkView = checkbox->checkView();
button->setClickedCallback([=]
{
checkView->setChecked(
!checkView->checked(),
anim::type::normal);
});
return checkView;
}();
checkView->checkedChanges(
) | start_with_next([=](bool checked)
{
},
verticalLayout->lifetime());
return checkView;
};
auto wrap = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container));
const auto verticalLayout = wrap->entity();
auto innerChecks = std::vector<not_null<Ui::AbstractCheckView*>>();
for (const auto &entry : checkboxes) {
const auto c = addCheckbox(verticalLayout, entry.checkboxLabel, entry.initial);
c->checkedValue(
) | start_with_next([=](bool enabled)
{
entry.callback(enabled);
},
container->lifetime());
innerChecks.push_back(c);
}
const auto raw = wrap.data();
raw->hide(anim::type::instant);
AddInnerToggle(
container,
st::powerSavingButtonNoIcon,
innerChecks,
raw,
std::move(title),
toggledWhenAll);
container->add(std::move(wrap));
container->widthValue(
) | start_with_next([=](int w)
{
raw->resizeToWidth(w);
},
raw->lifetime());
}
void AddChooseButtonWithIconAndRightTextInner(not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
int initialState,
std::vector<QString> options,
rpl::producer<QString> text,
rpl::producer<QString> boxTitle,
const style::SettingsButton &st,
Settings::IconDescriptor &&descriptor,
const Fn<void(int)> &setter) {
auto reactiveVal = container->lifetime().make_state<rpl::variable<int>>(initialState);
rpl::producer<QString> rightTextReactive = reactiveVal->value() | rpl::map(
[=](int val)
{
return options[val];
});
Settings::AddButtonWithLabel(
container,
std::move(text),
rightTextReactive,
st,
std::move(descriptor))->addClickHandler(
[=]
{
controller->show(Box(
[=](not_null<Ui::GenericBox*> box)
{
const auto save = [=](int index) mutable
{
setter(index);
reactiveVal->force_assign(index);
};
SingleChoiceBox(box,
{
.title = boxTitle,
.options = options,
.initialSelection = reactiveVal->current(),
.callback = save,
});
}));
});
}
void AddChooseButtonWithIconAndRightText(not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
int initialState,
std::vector<QString> options,
rpl::producer<QString> text,
rpl::producer<QString> boxTitle,
const style::icon &icon,
const Fn<void(int)> &setter) {
AddChooseButtonWithIconAndRightTextInner(
container,
controller,
initialState,
options,
std::move(text),
std::move(boxTitle),
st::settingsButton,
{&icon},
setter);
}
void AddChooseButtonWithIconAndRightText(not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
int initialState,
std::vector<QString> options,
rpl::producer<QString> text,
rpl::producer<QString> boxTitle,
const Fn<void(int)> &setter) {
AddChooseButtonWithIconAndRightTextInner(
container,
controller,
initialState,
options,
std::move(text),
std::move(boxTitle),
st::settingsButtonNoIcon,
{},
setter);
}
} // namespace Settings
@@ -0,0 +1,68 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
#include "settings/settings_common.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/wrap/slide_wrap.h"
namespace Window {
class SessionController;
} // namespace Window
namespace Settings {
struct NestedEntry
{
QString checkboxLabel;
bool initial;
std::function<void(bool)> callback;
};
rpl::producer<QString> asBeta(rpl::producer<QString> text);
not_null<Ui::RpWidget*> AddInnerToggle(not_null<Ui::VerticalLayout*> container,
const style::SettingsButton &st,
std::vector<not_null<Ui::AbstractCheckView*>> innerCheckViews,
not_null<Ui::SlideWrap<>*> wrap,
rpl::producer<QString> buttonLabel,
bool toggledWhenAll);
void AddCollapsibleToggle(not_null<Ui::VerticalLayout*> container,
rpl::producer<QString> title,
std::vector<NestedEntry> checkboxes,
bool toggledWhenAll);
void AddChooseButtonWithIconAndRightTextInner(not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
int initialState,
std::vector<QString> options,
rpl::producer<QString> text,
rpl::producer<QString> boxTitle,
const style::SettingsButton &st,
Settings::IconDescriptor &&descriptor,
const Fn<void(int)> &setter);
void AddChooseButtonWithIconAndRightText(not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
int initialState,
std::vector<QString> options,
rpl::producer<QString> text,
rpl::producer<QString> boxTitle,
const style::icon &icon,
const Fn<void(int)> &setter);
void AddChooseButtonWithIconAndRightText(not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
int initialState,
std::vector<QString> options,
rpl::producer<QString> text,
rpl::producer<QString> boxTitle,
const Fn<void(int)> &setter);
} // namespace Settings
@@ -0,0 +1,655 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "settings_chats.h"
#include <styles/style_ayu_icons.h>
#include "lang_auto.h"
#include "ayu/ayu_settings.h"
#include "ayu/ui/boxes/edit_mark_box.h"
#include "ayu/ui/settings/settings_ayu_utils.h"
#include "core/application.h"
#include "settings/settings_common.h"
#include "styles/style_ayu_styles.h"
#include "styles/style_boxes.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
#include "ui/vertical_list.h"
#include "ui/boxes/confirm_box.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/continuous_sliders.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
namespace Settings {
rpl::producer<QString> AyuChats::title() {
return tr::ayu_CategoryChats();
}
AyuChats::AyuChats(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Section(parent) {
setupContent(controller);
}
void SetupStickersAndEmojiSettings(not_null<Ui::VerticalLayout*> container) {
auto *settings = &AyuSettings::getInstance();
AddSubsectionTitle(container, tr::lng_settings_stickers_emoji()/*rpl::single(QString("Stickers and Emoji"))*/);
AddButtonWithIcon(
container,
tr::ayu_ShowOnlyAddedEmojisAndStickers(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->showOnlyAddedEmojisAndStickers)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showOnlyAddedEmojisAndStickers);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showOnlyAddedEmojisAndStickers(enabled);
AyuSettings::save();
},
container->lifetime());
std::vector checkboxes = {
NestedEntry{
tr::ayu_HideReactionsInChannels(tr::now), !settings->showChannelReactions, [=](bool enabled)
{
AyuSettings::set_hideChannelReactions(!enabled);
AyuSettings::save();
}
},
NestedEntry{
tr::ayu_HideReactionsInGroups(tr::now), !settings->showGroupReactions, [=](bool enabled)
{
AyuSettings::set_hideGroupReactions(!enabled);
AyuSettings::save();
}
}
};
AddCollapsibleToggle(container, tr::ayu_HideReactions(), checkboxes, false);
AddSkip(container);
AddDivider(container);
AddSkip(container);
}
void SetupRecentStickersLimit(not_null<Ui::VerticalLayout*> container) {
auto *settings = &AyuSettings::getInstance();
container->add(
object_ptr<Button>(container,
tr::ayu_SettingsRecentStickersCount(),
st::settingsButtonNoIcon)
)->setAttribute(Qt::WA_TransparentForMouseEvents);
auto recentStickersLimitSlider = MakeSliderWithLabel(
container,
st::autoDownloadLimitSlider,
st::settingsScaleLabel,
0,
st::settingsScaleLabel.style.font->width("8%%"));
container->add(std::move(recentStickersLimitSlider.widget), st::recentStickersLimitPadding);
const auto slider = recentStickersLimitSlider.slider;
const auto label = recentStickersLimitSlider.label;
const auto updateLabel = [=](int amount)
{
label->setText(QString::number(amount));
};
updateLabel(settings->recentStickersCount);
slider->setPseudoDiscrete(
200 + 1,
[=](int amount)
{
return amount;
},
settings->recentStickersCount,
[=](int amount)
{
updateLabel(amount);
},
[=](int amount)
{
updateLabel(amount);
AyuSettings::set_recentStickersCount(amount);
AyuSettings::save();
});
AddSkip(container);
AddDivider(container);
AddSkip(container);
}
void SetupGroupsAndChannels(not_null<Ui::VerticalLayout*> container, not_null<Window::SessionController*> controller) {
auto *settings = &AyuSettings::getInstance();
AddSubsectionTitle(container,
tr::lng_premium_double_limits_subtitle_channels()
/*rpl::single(QString("Groups and Channels"))*/);
const auto options = std::vector{
tr::ayu_ChannelBottomButtonHide(tr::now),
tr::ayu_ChannelBottomButtonMute(tr::now),
tr::ayu_ChannelBottomButtonDiscuss(tr::now),
};
AddChooseButtonWithIconAndRightText(
container,
controller,
settings->channelBottomButton,
options,
tr::ayu_ChannelBottomButton(),
tr::ayu_ChannelBottomButton(),
[=](int index)
{
AyuSettings::set_channelBottomButton(index);
AyuSettings::save();
});
AddButtonWithIcon(
container,
tr::ayu_QuickAdminShortcuts(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->quickAdminShortcuts)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->quickAdminShortcuts);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_quickAdminShortcuts(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_SettingsShowMessageShot(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->showMessageShot)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showMessageShot);
}) | start_with_next(
[=](bool enabled)
{
AyuSettings::set_showMessageShot(enabled);
AyuSettings::save();
},
container->lifetime());
AddSkip(container);
AddDividerText(container, tr::ayu_SettingsShowMessageShotDescription());
AddSkip(container);
}
void SetupMarks(not_null<Ui::VerticalLayout*> container) {
auto *settings = &AyuSettings::getInstance();
AddSubsectionTitle(container, tr::lng_settings_messages()/*rpl::single(QString("Messages"))*/);
AddButtonWithLabel(
container,
tr::ayu_DeletedMarkText(),
AyuSettings::get_deletedMarkReactive(),
st::settingsButtonNoIcon
)->addClickHandler(
[=]()
{
auto box = Box<EditMarkBox>(
tr::ayu_DeletedMarkText(),
settings->deletedMark,
QString("🧹"),
[=](const QString &value)
{
AyuSettings::set_deletedMark(value);
AyuSettings::save();
}
);
Ui::show(std::move(box));
});
AddButtonWithLabel(
container,
tr::ayu_EditedMarkText(),
AyuSettings::get_editedMarkReactive(),
st::settingsButtonNoIcon
)->addClickHandler(
[=]()
{
auto box = Box<EditMarkBox>(
tr::ayu_EditedMarkText(),
settings->editedMark,
tr::lng_edited(tr::now),
[=](const QString &value)
{
AyuSettings::set_editedMark(value);
AyuSettings::save();
}
);
Ui::show(std::move(box));
});
AddButtonWithIcon(
container,
tr::ayu_ReplaceMarksWithIcons(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->replaceBottomInfoWithIcons)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->replaceBottomInfoWithIcons);
}) | start_with_next(
[=](bool enabled)
{
AyuSettings::set_replaceBottomInfoWithIcons(enabled);
AyuSettings::save();
},
container->lifetime());
AddSkip(container);
AddDivider(container);
AddSkip(container);
AddButtonWithIcon(
container,
tr::ayu_HideShareButton(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->hideFastShare)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->hideFastShare);
}) | start_with_next(
[=](bool enabled)
{
AyuSettings::set_hideFastShare(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_SimpleQuotesAndReplies(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->simpleQuotesAndReplies)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->simpleQuotesAndReplies);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_simpleQuotesAndReplies(enabled);
AyuSettings::save();
},
container->lifetime());
AddSkip(container);
AddDivider(container);
AddSkip(container);
}
void SetupWideMessagesMultiplier(not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller) {
auto *settings = &AyuSettings::getInstance();
container->add(
object_ptr<Button>(container,
tr::ayu_SettingsWideMultiplier(),
st::settingsButtonNoIcon)
)->setAttribute(Qt::WA_TransparentForMouseEvents);
auto wideMultiplierSlider = MakeSliderWithLabel(
container,
st::autoDownloadLimitSlider,
st::settingsScaleLabel,
0,
st::settingsScaleLabel.style.font->width("8%%%"));
container->add(std::move(wideMultiplierSlider.widget), st::recentStickersLimitPadding);
const auto slider = wideMultiplierSlider.slider;
const auto label = wideMultiplierSlider.label;
const auto updateLabel = [=](double val)
{
label->setText(QString::number(val, 'f', 2));
};
constexpr auto kSizeAmount = 61; // (4.00 - 1.00) / 0.05 + 1
constexpr auto kMinSize = 1.00;
constexpr auto kStep = 0.05;
const auto valueToIndex = [=](double value)
{
return static_cast<int>(std::round((value - kMinSize) / kStep));
};
const auto indexToValue = [=](int index)
{
return kMinSize + index * kStep;
};
updateLabel(settings->wideMultiplier);
slider->setPseudoDiscrete(
kSizeAmount,
[=](int index) { return index; },
valueToIndex(settings->wideMultiplier),
[=](int index)
{
updateLabel(indexToValue(index));
},
[=](int index)
{
updateLabel(indexToValue(index));
AyuSettings::set_wideMultiplier(indexToValue(index));
AyuSettings::save();
// fix slider
crl::on_main([=]
{
controller->show(Ui::MakeConfirmBox({
.text = tr::lng_settings_need_restart(),
.confirmed = []
{
Core::Restart();
},
.confirmText = tr::lng_settings_restart_now(),
.cancelText = tr::lng_settings_restart_later(),
}));
});
});
AddSkip(container);
AddDividerText(container, tr::ayu_SettingsWideMultiplierDescription());
AddSkip(container);
}
void SetupContextMenuElements(not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller) {
auto *settings = &AyuSettings::getInstance();
AddSubsectionTitle(container, tr::ayu_ContextMenuElementsHeader());
const auto options = std::vector{
tr::ayu_SettingsContextMenuItemHidden(tr::now),
tr::ayu_SettingsContextMenuItemShown(tr::now),
tr::ayu_SettingsContextMenuItemExtended(tr::now),
};
AddChooseButtonWithIconAndRightText(
container,
controller,
settings->showReactionsPanelInContextMenu,
options,
tr::ayu_SettingsContextMenuReactionsPanel(),
tr::ayu_SettingsContextMenuTitle(),
st::menuIconReactions,
[=](int index)
{
AyuSettings::set_showReactionsPanelInContextMenu(index);
AyuSettings::save();
});
AddChooseButtonWithIconAndRightText(
container,
controller,
settings->showViewsPanelInContextMenu,
options,
tr::ayu_SettingsContextMenuViewsPanel(),
tr::ayu_SettingsContextMenuTitle(),
st::menuIconShowInChat,
[=](int index)
{
AyuSettings::set_showViewsPanelInContextMenu(index);
AyuSettings::save();
});
AddChooseButtonWithIconAndRightText(
container,
controller,
settings->showHideMessageInContextMenu,
options,
tr::ayu_ContextHideMessage(),
tr::ayu_SettingsContextMenuTitle(),
st::menuIconClear,
[=](int index)
{
AyuSettings::set_showHideMessageInContextMenu(index);
AyuSettings::save();
});
AddChooseButtonWithIconAndRightText(
container,
controller,
settings->showUserMessagesInContextMenu,
options,
tr::ayu_UserMessagesMenuText(),
tr::ayu_SettingsContextMenuTitle(),
st::menuIconTTL,
[=](int index)
{
AyuSettings::set_showUserMessagesInContextMenu(index);
AyuSettings::save();
});
AddChooseButtonWithIconAndRightText(
container,
controller,
settings->showMessageDetailsInContextMenu,
options,
tr::ayu_MessageDetailsPC(),
tr::ayu_SettingsContextMenuTitle(),
st::menuIconInfo,
[=](int index)
{
AyuSettings::set_showMessageDetailsInContextMenu(index);
AyuSettings::save();
});
AddSkip(container);
AddDividerText(container, tr::ayu_SettingsContextMenuDescription());
AddSkip(container);
}
void SetupMessageFieldElements(not_null<Ui::VerticalLayout*> container) {
auto *settings = &AyuSettings::getInstance();
AddSubsectionTitle(container, tr::ayu_MessageFieldElementsHeader());
AddButtonWithIcon(
container,
tr::ayu_MessageFieldElementAttach(),
st::settingsButton,
{&st::messageFieldAttachIcon}
)->toggleOn(
rpl::single(settings->showAttachButtonInMessageField)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showAttachButtonInMessageField);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showAttachButtonInMessageField(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_MessageFieldElementCommands(),
st::settingsButton,
{&st::messageFieldCommandsIcon}
)->toggleOn(
rpl::single(settings->showCommandsButtonInMessageField)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showCommandsButtonInMessageField);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showCommandsButtonInMessageField(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_MessageFieldElementTTL(),
st::settingsButton,
{&st::messageFieldTTLIcon}
)->toggleOn(
rpl::single(settings->showAutoDeleteButtonInMessageField)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showAutoDeleteButtonInMessageField);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showAutoDeleteButtonInMessageField(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_MessageFieldElementEmoji(),
st::settingsButton,
{&st::messageFieldEmojiIcon}
)->toggleOn(
rpl::single(settings->showEmojiButtonInMessageField)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showEmojiButtonInMessageField);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showEmojiButtonInMessageField(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_MessageFieldElementVoice(),
st::settingsButton,
{&st::messageFieldVoiceIcon}
)->toggleOn(
rpl::single(settings->showMicrophoneButtonInMessageField)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showMicrophoneButtonInMessageField);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showMicrophoneButtonInMessageField(enabled);
AyuSettings::save();
},
container->lifetime());
AddSkip(container);
AddDivider(container);
AddSkip(container);
}
void SetupMessageFieldPopups(not_null<Ui::VerticalLayout*> container) {
auto *settings = &AyuSettings::getInstance();
AddSubsectionTitle(container, tr::ayu_MessageFieldPopupsHeader());
AddButtonWithIcon(
container,
tr::ayu_MessageFieldElementAttach(),
st::settingsButton,
{&st::messageFieldAttachIcon}
)->toggleOn(
rpl::single(settings->showAttachPopup)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showAttachPopup);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showAttachPopup(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_MessageFieldElementEmoji(),
st::settingsButton,
{&st::messageFieldEmojiIcon}
)->toggleOn(
rpl::single(settings->showEmojiPopup)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showEmojiPopup);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_showEmojiPopup(enabled);
AyuSettings::save();
},
container->lifetime());
}
void AyuChats::setupContent(not_null<Window::SessionController*> controller) {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
AddSkip(content);
SetupStickersAndEmojiSettings(content);
SetupRecentStickersLimit(content);
SetupGroupsAndChannels(content, controller);
SetupMarks(content);
SetupWideMessagesMultiplier(content, controller);
SetupContextMenuElements(content, controller);
SetupMessageFieldElements(content);
SetupMessageFieldPopups(content);
AddSkip(content);
ResizeFitChild(this, content);
}
} // namespace Settings
@@ -0,0 +1,29 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
#include "settings/settings_common.h"
#include "settings/settings_common_session.h"
namespace Window {
class SessionController;
} // namespace Window
namespace Settings {
class AyuChats : public Section<AyuChats>
{
public:
AyuChats(QWidget *parent, not_null<Window::SessionController*> controller);
[[nodiscard]] rpl::producer<QString> title() override;
private:
void setupContent(not_null<Window::SessionController*> controller);
};
} // namespace Settings
@@ -0,0 +1,329 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "settings_general.h"
#include "lang_auto.h"
#include "settings_ayu_utils.h"
#include "ayu/ayu_settings.h"
#include "settings/settings_common.h"
#include "styles/style_settings.h"
#include "ui/vertical_list.h"
#include "ui/boxes/single_choice_box.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
namespace Settings {
rpl::producer<QString> AyuGeneral::title() {
return tr::ayu_CategoryGeneral();
}
AyuGeneral::AyuGeneral(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Section(parent) {
setupContent(controller);
}
void SetupTranslator(not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller) {
auto *settings = &AyuSettings::getInstance();
AddSubsectionTitle(container, tr::lng_translate_settings_subtitle()/*rpl::single(QString("Translate Messages"))*/);
const auto options = std::vector{
QString("Telegram"),
QString("Google"),
QString("Yandex")
};
const auto getIndex = [=](const QString &val)
{
if (val == "telegram") {
return 0;
}
if (val == "google") {
return 1;
}
if (val == "yandex") {
return 2;
}
return 0;
};
auto currentVal = AyuSettings::get_translationProviderReactive() | rpl::map(getIndex) | rpl::map(
[=](int val)
{
return options[val];
});
const auto button = AddButtonWithLabel(
container,
tr::ayu_TranslationProvider(),
currentVal,
st::settingsButtonNoIcon);
button->addClickHandler(
[=]
{
controller->show(Box(
[=](not_null<Ui::GenericBox*> box)
{
const auto save = [=](int index)
{
const auto provider = (index == 0) ? "telegram" : (index == 1) ? "google" : "yandex";
AyuSettings::set_translationProvider(provider);
AyuSettings::save();
};
SingleChoiceBox(box,
{
.title = tr::ayu_TranslationProvider(),
.options = options,
.initialSelection = getIndex(settings->translationProvider),
.callback = save,
});
}));
});
}
void SetupShowPeerId(not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller) {
auto *settings = &AyuSettings::getInstance();
const auto options = std::vector{
QString(tr::ayu_SettingsShowID_Hide(tr::now)),
QString("Telegram API"),
QString("Bot API")
};
auto currentVal = AyuSettings::get_showPeerIdReactive() | rpl::map(
[=](int val)
{
return options[val];
});
const auto button = AddButtonWithLabel(
container,
tr::ayu_SettingsShowID(),
currentVal,
st::settingsButtonNoIcon);
button->addClickHandler(
[=]
{
controller->show(Box(
[=](not_null<Ui::GenericBox*> box)
{
const auto save = [=](int index)
{
AyuSettings::set_showPeerId(index);
AyuSettings::save();
};
SingleChoiceBox(box,
{
.title = tr::ayu_SettingsShowID(),
.options = options,
.initialSelection = settings->showPeerId,
.callback = save,
});
}));
});
}
void SetupQoLToggles(not_null<Ui::VerticalLayout*> container, not_null<Window::SessionController*> controller) {
auto *settings = &AyuSettings::getInstance();
SetupTranslator(container, controller);
AddSkip(container);
AddDivider(container);
AddSkip(container);
AddSubsectionTitle(container, tr::ayu_CategoryGeneral());
AddButtonWithIcon(
container,
tr::ayu_DisableStories(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->disableStories)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->disableStories);
}) | rpl::start_with_next(
[=](bool enabled)
{
AyuSettings::set_disableStories(enabled);
AyuSettings::save();
},
container->lifetime());
std::vector checkboxes = {
NestedEntry{
tr::ayu_CollapseSimilarChannels(tr::now), settings->collapseSimilarChannels, [=](bool enabled)
{
AyuSettings::set_collapseSimilarChannels(enabled);
AyuSettings::save();
}
},
NestedEntry{
tr::ayu_HideSimilarChannelsTab(tr::now), settings->hideSimilarChannels, [=](bool enabled)
{
AyuSettings::set_hideSimilarChannels(enabled);
AyuSettings::save();
}
}
};
AddCollapsibleToggle(container, tr::ayu_DisableSimilarChannels(), checkboxes, true);
AddSkip(container);
AddDivider(container);
AddSkip(container);
AddButtonWithIcon(
container,
tr::ayu_SettingsShowMessageSeconds(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->showMessageSeconds)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->showMessageSeconds);
}) | start_with_next(
[=](bool enabled)
{
AyuSettings::set_showMessageSeconds(enabled);
AyuSettings::save();
},
container->lifetime());
SetupShowPeerId(container, controller);
AddSkip(container);
AddDivider(container);
AddSkip(container);
AddSubsectionTitle(container, rpl::single(QString("Webview")));
AddButtonWithIcon(
container,
tr::ayu_SettingsSpoofWebviewAsAndroid(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->spoofWebviewAsAndroid)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->spoofWebviewAsAndroid);
}) | start_with_next(
[=](bool enabled)
{
AyuSettings::set_spoofWebviewAsAndroid(enabled);
AyuSettings::save();
},
container->lifetime());
std::vector webviewCheckboxes = {
NestedEntry{
tr::ayu_SettingsIncreaseWebviewHeight(tr::now), settings->increaseWebviewHeight, [=](bool enabled)
{
AyuSettings::set_increaseWebviewHeight(enabled);
AyuSettings::save();
}
},
NestedEntry{
tr::ayu_SettingsIncreaseWebviewWidth(tr::now), settings->increaseWebviewWidth, [=](bool enabled)
{
AyuSettings::set_increaseWebviewWidth(enabled);
AyuSettings::save();
}
}
};
AddCollapsibleToggle(container, tr::ayu_SettingsBiggerWindow(), webviewCheckboxes, false);
AddSkip(container);
AddDivider(container);
AddSkip(container);
// todo: move into a single checkbox with dropdown
AddSubsectionTitle(container, tr::ayu_ConfirmationsTitle());
AddButtonWithIcon(
container,
tr::ayu_StickerConfirmation(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->stickerConfirmation)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->stickerConfirmation);
}) | start_with_next(
[=](bool enabled)
{
AyuSettings::set_stickerConfirmation(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_GIFConfirmation(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->gifConfirmation)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->gifConfirmation);
}) | start_with_next(
[=](bool enabled)
{
AyuSettings::set_gifConfirmation(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_VoiceConfirmation(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->voiceConfirmation)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->voiceConfirmation);
}) | start_with_next(
[=](bool enabled)
{
AyuSettings::set_voiceConfirmation(enabled);
AyuSettings::save();
},
container->lifetime());
}
void AyuGeneral::setupContent(not_null<Window::SessionController*> controller) {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
AddSkip(content);
SetupQoLToggles(content, controller);
AddSkip(content);
ResizeFitChild(this, content);
}
} // namespace Settings
@@ -0,0 +1,29 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
#include "settings/settings_common.h"
#include "settings/settings_common_session.h"
namespace Window {
class SessionController;
} // namespace Window
namespace Settings {
class AyuGeneral : public Section<AyuGeneral>
{
public:
AyuGeneral(QWidget *parent, not_null<Window::SessionController*> controller);
[[nodiscard]] rpl::producer<QString> title() override;
private:
void setupContent(not_null<Window::SessionController*> controller);
};
} // namespace Settings
@@ -0,0 +1,224 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "settings_main.h"
#include <QDesktopServices>
#include <styles/style_ayu_icons.h>
#include "lang_auto.h"
#include "settings_appearance.h"
#include "settings_ayu.h"
#include "settings_chats.h"
#include "settings_general.h"
#include "settings_other.h"
#include "ayu/ayu_settings.h"
#include "ayu/ui/ayu_logo.h"
#include "core/version.h"
#include "settings/settings_common.h"
#include "styles/style_ayu_settings.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
#include "window/window_session_controller_link_info.h"
namespace Settings {
rpl::producer<QString> AyuMain::title() {
return rpl::single(QString(""));
}
AyuMain::AyuMain(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Section(parent) {
setupContent(controller);
}
void SetupAppLogo(not_null<Ui::VerticalLayout*> container) {
const auto logo = container->add(
object_ptr<Ui::CenterWrap<Ui::RpWidget>>(
container,
object_ptr<Ui::RpWidget>(container)));
const auto widget = logo->entity();
widget->resize(QSize(st::settingsCloudPasswordIconSize, st::settingsCloudPasswordIconSize));
widget->paintRequest(
) | rpl::start_with_next([=](QRect clip)
{
auto p = QPainter(widget);
const auto image = AyuAssets::currentAppLogoNoMargin(); // todo: svg renderer
if (!image.isNull()) {
const auto size = st::settingsCloudPasswordIconSize;
const auto scaled = image.scaled(
size * style::DevicePixelRatio(),
size * style::DevicePixelRatio(),
Qt::KeepAspectRatio,
Qt::SmoothTransformation);
p.drawImage(
QRect(0, 0, size, size),
scaled);
}
},
widget->lifetime());
}
void SetupCategories(
not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
Fn<void(Type)> showOther) {
struct CategoryInfo
{
QString name;
const style::icon *icon;
std::function<void()> handler;
};
const auto categories = std::vector<CategoryInfo>{
{QString("AyuGram"), &st::menuIconGroupReactions, [=] { showOther(AyuGhost::Id()); }},
// {QString("Filters"), &st::menuIconTagFilter, [=] { showOther(AyuFilters::Id()); }},
{tr::ayu_CategoryGeneral(tr::now), &st::menuIconShowAll, [=] { showOther(AyuGeneral::Id()); }},
{tr::ayu_CategoryAppearance(tr::now), &st::menuIconPalette, [=] { showOther(AyuAppearance::Id()); }},
{tr::ayu_CategoryChats(tr::now), &st::menuIconChatBubble, [=] { showOther(AyuChats::Id()); }},
{tr::ayu_CategoryOther(tr::now), &st::menuIconFave, [=] { showOther(AyuOther::Id()); }},
};
for (const auto &category : categories) {
AddButtonWithIcon(
container,
rpl::single(category.name),
st::settingsButton,
{category.icon}
)->setClickedCallback([=]
{
if (category.handler) {
category.handler();
}
});
}
}
void SetupLinks(
not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller) {
struct LinkInfo
{
QString name;
QString value;
const style::icon *icon;
std::function<void()> handler;
};
const auto links = std::vector<LinkInfo>{
{
tr::ayu_LinksChannel(tr::now),
QString("@ayugram"),
&st::menuIconChannel,
[=]
{
controller->showPeerByLink(Window::PeerByLinkInfo{
.usernameOrId = QString("ayugram"),
});
}
},
{
tr::ayu_LinksChats(tr::now),
QString("@ayugramchat"),
&st::menuIconChats,
[=]
{
controller->showPeerByLink(Window::PeerByLinkInfo{
.usernameOrId = QString("ayugramchat"),
});
}
},
{
tr::ayu_LinksTranslate(tr::now),
QString("Crowdin"),
&st::menuIconTranslate,
[=]
{
QDesktopServices::openUrl(QString("https://translate.ayugram.one"));
}
},
{
tr::ayu_LinksDocumentation(tr::now),
QString("docs.ayugram.one"),
&st::menuIconIpAddress,
[=]
{
QDesktopServices::openUrl(QString("https://docs.ayugram.one"));
}
},
};
for (const auto &link : links) {
AddButtonWithLabel(
container,
rpl::single(link.name),
rpl::single(link.value),
st::settingsButton,
{link.icon}
)->setClickedCallback(link.handler);
}
}
void AyuMain::setupContent(not_null<Window::SessionController*> controller) {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
SetupAppLogo(content);
AddSkip(content);
content->add(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
content,
object_ptr<Ui::FlatLabel>(
content,
rpl::single(QString("AyuGram Desktop v") + QString::fromLatin1(AppVersionStr)),
st::boxTitle)));
AddSkip(content);
content->add(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
content,
object_ptr<Ui::FlatLabel>(
content,
tr::ayu_SettingsDescription(),
st::centeredBoxLabel)));
AddSkip(content);
AddSkip(content);
AddSkip(content);
AddSkip(content);
AddDivider(content);
AddSkip(content);
AddSubsectionTitle(content, tr::ayu_CategoriesHeader());
SetupCategories(content, controller, showOtherMethod());
AddSkip(content);
AddDivider(content);
AddSkip(content);
AddSubsectionTitle(content, tr::ayu_LinksHeader());
SetupLinks(content, controller);
AddSkip(content);
ResizeFitChild(this, content);
}
} // namespace Settings
@@ -0,0 +1,32 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
#include "settings/settings_common.h"
#include "settings/settings_common_session.h"
class BoxContent;
namespace Window {
class Controller;
class SessionController;
}
namespace Settings {
class AyuMain : public Section<AyuMain>
{
public:
AyuMain(QWidget *parent, not_null<Window::SessionController*> controller);
[[nodiscard]] rpl::producer<QString> title() override;
private:
void setupContent(not_null<Window::SessionController*> controller);
};
}
@@ -0,0 +1,250 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "settings_other.h"
#include <QDesktopServices>
#include <QGuiApplication>
#include <QSvgRenderer>
#include "lang_auto.h"
#include "ayu/ayu_settings.h"
#include "ayu/ui/boxes/donate_qr_box.h"
#include "boxes/abstract_box.h"
#include "core/application.h"
#include "lang/lang_text_entity.h"
#include "main/main_session.h"
#include "settings/settings_common.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
#include "ui/integration.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/vertical_list.h"
#include "ui/boxes/confirm_box.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
#include "window/themes/window_theme.h"
namespace Settings {
namespace {
struct Asset
{
QString icon;
QColor background;
};
Asset getAsset(const QString &name) {
const auto isNightMode = Window::Theme::IsNightMode();
const auto normalized = name.toLower();
QString icon = QString(":/gui/icons/ayu/donates/%1.svg").arg(normalized);
QColor background = isNightMode ? QColor(0xEEEEEE) : QColor(0x242B2C);
return {
.icon = std::move(icon),
.background = std::move(background)
};
}
QImage getImage(const QString &name) {
const auto iconData = getAsset(name);
const auto factor = style::DevicePixelRatio();
const auto size = st::menuIconLink.size();
auto image = QImage(
size * factor,
QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(factor);
image.fill(Qt::transparent);
{
auto p = QPainter(&image);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(iconData.background);
p.drawRoundedRect(Rect(size), size.width() / 4., size.height() / 4.);
p.setBrush(Qt::transparent);
auto svgIcon = QSvgRenderer(iconData.icon);
svgIcon.render(&p, Rect(size));
}
return image;
}
[[nodiscard]] not_null<Ui::SettingsButton*> AddDonate(not_null<Ui::SettingsButton*> button, const QString &name) {
const auto btnContainer = Ui::CreateChild<Ui::RpWidget>(button);
const auto &buttonSt = button->st();
const auto fullHeight = buttonSt.height
+ rect::m::sum::v(buttonSt.padding);
const auto iconWidget = Ui::CreateChild<Ui::RpWidget>(button.get());
auto icon = getImage(name);
iconWidget->resize(icon.size() / style::DevicePixelRatio());
iconWidget->paintRequest(
) | rpl::start_with_next([=]
{
auto p = QPainter(iconWidget);
p.drawImage(0, 0, icon);
},
iconWidget->lifetime());
button->sizeValue(
) | rpl::start_with_next([=](const QSize &s)
{
iconWidget->moveToLeft(
button->st().iconLeft
+ (st::menuIconShop.width() - iconWidget->width()) / 2,
(s.height() - iconWidget->height()) / 2);
btnContainer->moveToLeft(
iconWidget->x() - (fullHeight - iconWidget->height()) / 2,
0);
},
iconWidget->lifetime());
btnContainer->resize(fullHeight, fullHeight);
return button;
}
void AddCryptoDonate(const QString &name, const QString &address, not_null<Ui::VerticalLayout*> container) {
const auto button = AddDonate(
AddButtonWithIcon(
container,
rpl::single(name),
st::settingsButton),
name);
button->setClickedCallback([=]
{
auto box = Box(
Ui::FillDonateQrBox,
address,
getAsset(name).icon);
Ui::show(std::move(box));
});
}
}
rpl::producer<QString> AyuOther::title() {
return tr::ayu_CategoryOther();
}
AyuOther::AyuOther(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Section(parent) {
setupContent(controller);
}
void SetupDonations(not_null<Ui::VerticalLayout*> container, not_null<Window::SessionController*> controller) {
AddSubsectionTitle(container, tr::ayu_SupportHeader());
AddDonate(
AddButtonWithIcon(
container,
rpl::single(QString("Boosty")),
st::settingsButton),
"boosty"
)->setClickedCallback([=]
{
QDesktopServices::openUrl(QString("https://boosty.to/alexeyzavar"));
});
AddCryptoDonate("TON", QString("UQA4i8U8vP3mYUZSV3KqDQEHPwmhninEqCkkKc7BITQ652de"), container);
AddCryptoDonate("Bitcoin", QString("bc1qdk6qq4mzq5yap3fpy0qau3246w3m3uwac9f0xd"), container);
AddCryptoDonate("Ethereum", QString("0x405589857C8DFAb45B2027c68ad1e58877FDa347"), container);
AddCryptoDonate("Solana", QString("8ZHQpPxpsdRjsWoBcF1dmvRM5dB6zEhJ3jMBFZjYfyHs"), container);
AddCryptoDonate("Tron", QString("TRpbajq38qU8joThgAfKJLyEPbNjzsdPJ1"), container);
AddSkip(container);
AddDividerText(container,
tr::ayu_SupportDescription2(
lt_item,
rpl::single(
Ui::Text::Link(tr::ayu_SupportDescription1(tr::now), QString("tg://support"))
),
Ui::Text::WithEntities
)
);
}
void SetupCrashReporting(not_null<Ui::VerticalLayout*> container) {
auto *settings = &AyuSettings::getInstance();
AddSkip(container);
AddSubsectionTitle(container, tr::ayu_CategoryOther());
AddButtonWithIcon(
container,
tr::ayu_CrashReporting(),
st::settingsButton,
{&st::menuIconReport}
)->toggleOn(
rpl::single(settings->crashReporting)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->crashReporting);
}) | start_with_next(
[=](bool enabled)
{
AyuSettings::set_crashReporting(enabled);
AyuSettings::save();
},
container->lifetime());
AddSkip(container);
AddDividerText(container, tr::ayu_CrashReportingDescription());
}
void SetupOtherThings(not_null<Ui::VerticalLayout*> container, not_null<Window::SessionController*> controller) {
AddSkip(container);
AddButtonWithIcon(
container,
tr::ayu_RegisterURLScheme(),
st::settingsButton,
{&st::menuIconLink}
)->setClickedCallback([=]
{
Core::Application::RegisterUrlScheme();
controller->showToast(tr::lng_box_done(tr::now));
});
AddButtonWithIcon(
container,
tr::ayu_ResetSettings(),
st::settingsButton,
{&st::menuIconRestore}
)->setClickedCallback([=]
{
controller->show(Ui::MakeConfirmBox({
.text = tr::ayu_ResetSettingsConfirmation(Ui::Text::RichLangValue),
.confirmed = [=](Fn<void()> &&close)
{
AyuSettings::reset();
close();
},
.confirmText = tr::lng_box_yes(),
}));
});
AddSkip(container);
}
void AyuOther::setupContent(not_null<Window::SessionController*> controller) {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
AddSkip(content);
SetupDonations(content, controller);
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
SetupCrashReporting(content);
#endif
SetupOtherThings(content, controller);
ResizeFitChild(this, content);
}
} // namespace Settings
@@ -0,0 +1,29 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
#include "settings/settings_common.h"
#include "settings/settings_common_session.h"
namespace Window {
class SessionController;
} // namespace Window
namespace Settings {
class AyuOther : public Section<AyuOther>
{
public:
AyuOther(QWidget *parent, not_null<Window::SessionController*> controller);
[[nodiscard]] rpl::producer<QString> title() override;
private:
void setupContent(not_null<Window::SessionController*> controller);
};
} // namespace Settings
@@ -12,7 +12,7 @@
constexpr auto kMaxChannelId = -1000000000000;
QString IDString(not_null<PeerData*> peer) {
QString IDString(const not_null<PeerData*> peer) {
auto resultId = QString::number(getBareID(peer));
const auto &settings = AyuSettings::getInstance();
@@ -27,10 +27,8 @@ QString IDString(not_null<PeerData*> peer) {
return resultId;
}
QString IDString(MsgId topic_root_id) {
auto resultId = QString::number(topic_root_id.bare);
return resultId;
QString IDString(MsgId topicRootId) {
return QString::number(topicRootId.bare);
}
rpl::producer<TextWithEntities> IDValue(not_null<PeerData*> peer) {
@@ -39,4 +37,4 @@ rpl::producer<TextWithEntities> IDValue(not_null<PeerData*> peer) {
rpl::producer<TextWithEntities> IDValue(MsgId topicRootId) {
return rpl::single(IDString(topicRootId)) | Ui::Text::ToWithEntities();
}
}
@@ -7,7 +7,7 @@
#pragma once
QString IDString(not_null<PeerData*> peer);
QString IDString(MsgId topic_root_id);
QString IDString(MsgId topicRootId);
rpl::producer<TextWithEntities> IDValue(not_null<PeerData*> peer);
rpl::producer<TextWithEntities> IDValue(MsgId topicRootId);
rpl::producer<TextWithEntities> IDValue(MsgId topicRootId);
+48 -7
View File
@@ -51,7 +51,7 @@ void RCManager::makeRequest() {
clearSentRequest();
const auto request = QNetworkRequest(QUrl("https://update.ayugram.one/rc/current/desktop"));
const auto request = QNetworkRequest(QUrl("https://update.ayugram.one/rc/current/desktop2"));
_reply = _manager->get(request);
connect(_reply,
&QNetworkReply::finished,
@@ -105,12 +105,16 @@ bool RCManager::applyResponse(const QByteArray &response) {
const auto root = document.object();
const auto developers = root.value("developers").toArray();
const auto channels = root.value("channels").toArray();
const auto officialChannels = root.value("officialChannels").toArray();
const auto supporters = root.value("supporters").toArray();
const auto supporterChannels = root.value("supporterChannels").toArray();
const auto customBadges = root.value("customBadges").toArray();
_developers.clear();
_channels.clear();
_officialChannels.clear();
_supporters.clear();
_supporterChannels.clear();
_customBadges.clear();
for (const auto &developer : developers) {
if (const auto id = developer.toVariant().toLongLong()) {
@@ -118,9 +122,9 @@ bool RCManager::applyResponse(const QByteArray &response) {
}
}
for (const auto &channel : channels) {
for (const auto &channel : officialChannels) {
if (const auto id = channel.toVariant().toLongLong()) {
_channels.insert(id);
_officialChannels.insert(id);
}
}
@@ -130,10 +134,47 @@ bool RCManager::applyResponse(const QByteArray &response) {
}
}
for (const auto &channel : supporterChannels) {
if (const auto id = channel.toVariant().toLongLong()) {
_supporterChannels.insert(id);
}
}
for (const auto &badge : customBadges) {
if (!badge.isObject()) {
continue;
}
const auto obj = badge.toObject();
const auto id = obj.value("id").toVariant().toLongLong();
if (!id) {
continue;
}
const auto badgeObj = obj.value("badge");
if (!badgeObj.isObject()) {
continue;
}
const auto badgeData = badgeObj.toObject();
CustomBadge customBadge;
if (const auto emojiStatusId = badgeData.value("documentId").toVariant().toLongLong()) {
customBadge.emojiStatusId = EmojiStatusId(emojiStatusId);
} else {
continue;
}
if (const auto text = badgeData.value("text").toString(); !text.isEmpty()) {
customBadge.text = text;
}
_customBadges[id] = customBadge;
}
_donateUsername = root.value("donateUsername").toString();
_donateAmountUsd = root.value("donateAmountUsd").toString();
_donateAmountTon = root.value("donateAmountTon").toString();
_donateAmountRub = root.value("donateAmountRub").toString();
initialized = true;
LOG(("RCManager: Loaded %1 developers, %2 channels"
).arg(_developers.size()).arg(_channels.size()));
LOG(("RCManager: Loaded %1 developers, %2 official channels"
).arg(_developers.size()).arg(_officialChannels.size()));
return true;
}
+39 -2
View File
@@ -13,6 +13,12 @@
extern std::unordered_set<ID> default_developers;
extern std::unordered_set<ID> default_channels;
struct CustomBadge
{
EmojiStatusId emojiStatusId;
QString text;
};
class RCManager final : public QObject
{
Q_OBJECT
@@ -40,13 +46,37 @@ public:
if (!initialized) {
return default_channels;
}
return _channels;
return _officialChannels;
}
[[nodiscard]] const std::unordered_set<ID> &supporters() const {
return _supporters;
}
[[nodiscard]] const std::unordered_set<ID> &supporterChannels() const {
return _supporterChannels;
}
[[nodiscard]] const std::unordered_map<ID, CustomBadge> &supporterCustomBadges() const {
return _customBadges;
}
[[nodiscard]] QString donateUsername() const {
return _donateUsername;
}
[[nodiscard]] QString donateAmountUsd() const {
return _donateAmountUsd;
}
[[nodiscard]] QString donateAmountTon() const {
return _donateAmountTon;
}
[[nodiscard]] QString donateAmountRub() const {
return _donateAmountRub;
}
private:
RCManager() = default;
~RCManager();
@@ -62,8 +92,15 @@ private:
bool initialized = false;
std::unordered_set<ID> _developers = {};
std::unordered_set<ID> _channels = {};
std::unordered_set<ID> _officialChannels = {};
std::unordered_set<ID> _supporters = {};
std::unordered_set<ID> _supporterChannels = {};
std::unordered_map<ID, CustomBadge> _customBadges = {};
QString _donateUsername = QString("@ayugramOwner");
QString _donateAmountUsd = QString("4.00");
QString _donateAmountTon = QString("1.30");
QString _donateAmountRub = QString("300");
QTimer* _timer = nullptr;
@@ -92,16 +92,37 @@ bool isExteraPeer(ID peerId) {
}
bool isSupporterPeer(ID peerId) {
return RCManager::getInstance().supporters().contains(peerId);
return RCManager::getInstance().supporters().contains(peerId) || RCManager::getInstance().supporterChannels().contains(peerId);
}
bool isCustomBadgePeer(ID peerId) {
return RCManager::getInstance().supporterCustomBadges().contains(peerId);
}
CustomBadge getCustomBadge(ID peerId) {
const auto &badges = RCManager::getInstance().supporterCustomBadges();
if (const auto it = badges.find(peerId); it != badges.end()) {
return it->second;
}
return {};
}
rpl::producer<Info::Profile::Badge::Content> ExteraBadgeTypeFromPeer(not_null<PeerData*> peer) {
if (isExteraPeer(getBareID(peer))) {
return rpl::single(Info::Profile::Badge::Content{Info::Profile::BadgeType::Extera });
if (isCustomBadgePeer(getBareID(peer))) {
return rpl::single(Info::Profile::Badge::Content{
.badge = Info::Profile::BadgeType::ExteraCustom,
.emojiStatusId = getCustomBadge(getBareID(peer)).emojiStatusId
});
} else if (isExteraPeer(getBareID(peer))) {
return rpl::single(Info::Profile::Badge::Content{
.badge = Info::Profile::BadgeType::Extera
});
} else if (isSupporterPeer(getBareID(peer))) {
return rpl::single(Info::Profile::Badge::Content{Info::Profile::BadgeType::ExteraSupporter });
return rpl::single(Info::Profile::Badge::Content{
.badge = Info::Profile::BadgeType::ExteraSupporter
});
}
return rpl::single(Info::Profile::Badge::Content{Info::Profile::BadgeType::None });
return rpl::single(Info::Profile::Badge::Content{Info::Profile::BadgeType::None});
}
bool isMessageHidden(const not_null<HistoryItem*> item) {
@@ -6,6 +6,7 @@
// Copyright @Radolyn, 2025
#pragma once
#include "rc_manager.h"
#include "ayu/data/entities.h"
#include "core/application.h"
@@ -25,6 +26,8 @@ ID getBareID(not_null<PeerData*> peer);
bool isExteraPeer(ID peerId);
bool isSupporterPeer(ID peerId);
bool isCustomBadgePeer(ID peerId);
CustomBadge getCustomBadge(ID peerId);
rpl::producer<Info::Profile::Badge::Content> ExteraBadgeTypeFromPeer(not_null<PeerData*> peer);
+13 -1
View File
@@ -51,6 +51,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
// AyuGram includes
#include "ayu/ayu_settings.h"
namespace {
using Language = Lang::Language;
@@ -1196,7 +1200,15 @@ void LanguageBox::setupTop(not_null<Ui::VerticalLayout*> container) {
}, translateEnabled->lifetime());
using namespace rpl::mappers;
auto premium = Data::AmPremiumValue(&_controller->session());
auto premium = Data::AmPremiumValue(&_controller->session()) | rpl::map([=](bool val)
{
// const auto &settings = AyuSettings::getInstance();
// if (settings.translationProvider != "telegram") {
// return true;
// }
// return val;
return true;
});
const auto translateChat = container->add(object_ptr<Ui::SettingsButton>(
container,
tr::lng_translate_settings_chat(),
+7 -2
View File
@@ -38,6 +38,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QLocale>
// AyuGram includes
#include "ayu/features/translator/ayu_translator.h"
namespace Ui {
namespace {
@@ -226,7 +230,8 @@ void TranslateBox(
const auto send = [=](LanguageId to) {
loading->show(anim::type::instant);
translated->hide(anim::type::instant);
state->api.request(MTPmessages_TranslateText(
Ayu::Translator::TranslateManager::currentInstance()->request(
peer->session(),
MTP_flags(flags),
msgId ? peer->input : MTP_inputPeerEmpty(),
(msgId
@@ -241,7 +246,7 @@ void TranslateBox(
text.entities,
Api::ConvertOption::SkipLocal)))),
MTP_string(to.twoLetterCode())
)).done([=](const MTPmessages_TranslatedText &result) {
).done([=](const MTPmessages_TranslatedText &result) {
const auto &data = result.data();
const auto &list = data.vresult().v;
if (list.isEmpty()) {
@@ -23,6 +23,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QStandardPaths>
#include <QtCore/QTimer>
// AyuGram includes
#include "ayu/ayu_settings.h"
namespace {
constexpr auto kDefaultProxyPort = 80;
@@ -273,11 +277,11 @@ LastCrashedWindow::LastCrashedWindow(
excludeReportUsername();
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
if (false) {
const auto &settings = AyuSettings::getInstance();
if (!settings.crashReporting) {
#else
if (true) {
#endif
// Currently accept crash reports only from testers.
_sendingState = SendingNoReport;
} else if (Core::OpenGLLastCheckFailed()) {
// Nothing we can do right now with graphics driver crashes in GL.
@@ -1643,9 +1643,13 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
AyuUrlHandlers::ResolveUser
},
{
u"^ayu/?(.+)?(#|$)"_q,
u"^ayu(/?.+)?(#|$)"_q,
AyuUrlHandlers::HandleAyu
},
{
u"^support$"_q,
AyuUrlHandlers::HandleSupport
},
{
u"^([^\\?]+)(\\?|#|$)"_q,
HandleUnknown
@@ -53,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_dialogs.h"
// AyuGram includes
#include "ayu/ayu_settings.h"
#include "ayu/features/messageshot/message_shot.h"
#include "styles/style_ayu_icons.h"
@@ -4042,6 +4043,11 @@ std::optional<QSize> Message::rightActionSize() const {
}
bool Message::displayFastShare() const {
const auto &settings = AyuSettings::getInstance();
if (settings.hideFastShare) {
return false;
}
const auto item = data();
const auto peer = item->history()->peer;
if (!item->allowsForward()) {
@@ -1270,6 +1270,10 @@ void TopBarWidget::updateControlsVisibility() {
const auto showRecentActions = [&]
{
const auto &settings = AyuSettings::getInstance();
if (!settings.quickAdminShortcuts) {
return false;
}
if (_activeChat.section == Section::ChatsList) {
return false;
}
@@ -1284,6 +1288,10 @@ void TopBarWidget::updateControlsVisibility() {
_recentActions->setVisible(showRecentActions);
const auto showAdmins = [&]
{
const auto &settings = AyuSettings::getInstance();
if (!settings.quickAdminShortcuts) {
return false;
}
if (_activeChat.section == Section::ChatsList) {
return false;
}
@@ -23,6 +23,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "spellcheck/platform/platform_language.h"
// AyuGram includes
#include "ayu/features/translator/ayu_translator.h"
namespace HistoryView {
namespace {
@@ -240,7 +244,7 @@ void TranslateTracker::cancelSentRequest() {
item->translationShowRequiresRequest({});
}
}
_history->session().api().request(base::take(_requestId)).cancel();
Ayu::Translator::TranslateManager::currentInstance()->cancel(_requestId);
}
}
@@ -277,13 +281,14 @@ void TranslateTracker::requestSome() {
}
}
using Flag = MTPmessages_TranslateText::Flag;
_requestId = session->api().request(MTPmessages_TranslateText(
_requestId = Ayu::Translator::TranslateManager::currentInstance()->request(
peer->session(),
MTP_flags(Flag::f_peer | Flag::f_id),
peer->input,
MTP_vector<MTPint>(list),
MTPVector<MTPTextWithEntities>(),
MTP_string(to.twoLetterCode())
)).done([=](const MTPmessages_TranslatedText &result) {
).done([=](const MTPmessages_TranslatedText &result) {
requestDone(to, result.data().vresult().v);
}).fail([=] {
requestDone(to, {});
@@ -53,9 +53,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_menu_icons.h"
#include "styles/style_layers.h"
// AyuGram includes
#include "ayu/ui/settings/settings_ayu.h"
namespace Info {
namespace {
@@ -32,7 +32,8 @@ namespace {
return content.badge == BadgeType::Premium
|| (content.badge == BadgeType::Verified && content.emojiStatusId)
|| (content.badge == BadgeType::Extera)
|| (content.badge == BadgeType::ExteraSupporter);
|| (content.badge == BadgeType::ExteraSupporter)
|| (content.badge == BadgeType::ExteraCustom);
}
} // namespace
@@ -88,6 +89,7 @@ void Badge::setContent(Content content) {
_view.create(_parent);
_view->show();
switch (_content.badge) {
case BadgeType::ExteraCustom:
case BadgeType::Verified:
case BadgeType::BotVerified:
case BadgeType::Premium: {
@@ -35,7 +35,7 @@ namespace Info::Profile {
class EmojiStatusPanel;
enum class BadgeType : uchar {
enum class BadgeType : ushort {
None = 0x00,
Verified = 0x01,
BotVerified = 0x02,
@@ -44,7 +44,8 @@ enum class BadgeType : uchar {
Fake = 0x10,
Direct = 0x20,
Extera = 0x40,
ExteraSupporter = 0x80, // todo: remove `uchar` if they add more badges
ExteraSupporter = 0x80,
ExteraCustom = 0x100,
};
inline constexpr bool is_flag_type(BadgeType) { return true; }
@@ -685,17 +685,43 @@ Cover::Cover(
::Settings::ShowEmojiStatusPremium(_controller, _peer);
}
});
if (_peer->isUser() && (isExteraPeer(getBareID(_peer)) || isSupporterPeer(getBareID(_peer)))) {
const auto isCustomBadge = isCustomBadgePeer(getBareID(_peer));
const auto isExtera = isExteraPeer(getBareID(_peer));
const auto isSupporter = isSupporterPeer(getBareID(_peer));
if (isExtera || isSupporter || isCustomBadge) {
_exteraBadge->setPremiumClickCallback([=]
{
TextWithEntities text;
if (isExteraPeer(getBareID(_peer))) {
text = tr::ayu_DeveloperPopup(
tr::now,
lt_item,
TextWithEntities{_peer->name()},
Ui::Text::RichLangValue);
} else if (isSupporterPeer(getBareID(_peer))) {
if (isCustomBadge) {
const auto custom = getCustomBadge(getBareID(_peer));
text = custom.text.isEmpty()
? (isExtera
? tr::ayu_DeveloperPopup(
tr::now,
lt_item,
TextWithEntities{_peer->name()},
Ui::Text::RichLangValue)
: tr::ayu_SupporterPopup(
tr::now,
lt_item,
TextWithEntities{_peer->name()},
Ui::Text::RichLangValue))
: Ui::Text::RichLangValue(custom.text);
} else if (isExtera) {
text = _peer->isUser()
? tr::ayu_DeveloperPopup(
tr::now,
lt_item,
TextWithEntities{_peer->name()},
Ui::Text::RichLangValue)
: tr::ayu_OfficialResourcePopup(
tr::now,
lt_item,
TextWithEntities{_peer->name()},
Ui::Text::RichLangValue);
} else if (isSupporter) {
text = tr::ayu_SupporterPopup(
tr::now,
lt_item,
@@ -131,7 +131,7 @@ struct LinkWithUrl {
[[nodiscard]] rpl::producer<bool> CanViewParticipantsValue(
not_null<ChannelData*> megagroup);
enum class BadgeType : uchar;
enum class BadgeType : ushort;
[[nodiscard]] rpl::producer<BadgeType> BadgeValue(not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<EmojiStatusId> EmojiStatusIdValue(
not_null<PeerData*> peer);
@@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_information.h"
#include "ui/ui_utility.h"
// AyuGram includes
#include "ayu/ui/settings/settings_main.h"
namespace Info {
namespace Settings {
@@ -227,7 +231,8 @@ const Ui::RoundRect *Widget::bottomSkipRounding() const {
rpl::producer<bool> Widget::desiredShadowVisibility() const {
return (_type == ::Settings::Main::Id()
|| _type == ::Settings::Information::Id())
|| _type == ::Settings::Information::Id()
|| _type == ::Settings::AyuMain::Id())
? ContentWidget::desiredShadowVisibility()
: rpl::single(true);
}
@@ -83,7 +83,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_dialogs.h"
// AyuGram includes
#include "ayu/ui/settings/settings_ayu.h"
#include "ayu/features/messageshot/message_shot.h"
#include "window/themes/window_theme_preview.h"
@@ -11,17 +11,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_chat.h"
#include "settings/settings_main.h"
// AyuGram includes
#include "ayu/ui/settings/settings_ayu.h"
namespace Settings {
bool HasMenu(Type type) {
return (type == ::Settings::CloudPasswordEmailConfirmId())
|| (type == Main::Id())
|| (type == Chat::Id())
|| (type == Ayu::Id());
|| (type == Chat::Id());
}
} // namespace Settings
@@ -84,7 +84,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QWindow>
// AyuGram includes
#include "ayu/ui/settings/settings_ayu.h"
#include "ayu/ui/settings/settings_main.h"
#include "ayu/ui/utils/ayu_profile_values.h"
#include "ayu/utils/telegram_helpers.h"
@@ -657,7 +657,7 @@ void SetupSections(
Ui::AddSkip(container);
addSection(
tr::ayu_AyuPreferences(),
Ayu::Id(),
AyuMain::Id(),
{ .icon = &st::menuIconPremium });
Ui::AddSkip(container);
Ui::AddDivider(container);
-1
View File
@@ -17,7 +17,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
// AyuGram includes
#include "ayu/ayu_settings.h"
#include "ayu/ui/settings/settings_ayu.h"
#include "ayu/features/streamer_mode/streamer_mode.h"
#include "lang_auto.h"
@@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/chat/chat_style.h"
#include "styles/style_chat.h"
// AyuGram includes
#include "ayu/ayu_settings.h"
namespace Ui {
namespace {
@@ -32,8 +36,8 @@ void PaintBubbleGeneric(
const auto topLeft = args.rounding.topLeft;
const auto topRight = args.rounding.topRight;
const auto bottomWithTailLeft = args.rounding.bottomLeft;
const auto bottomWithTailRight = args.rounding.bottomRight;
auto bottomWithTailLeft = args.rounding.bottomLeft;
auto bottomWithTailRight = args.rounding.bottomRight;
if (topLeft == Corner::None
&& topRight == Corner::None
&& bottomWithTailLeft == Corner::None
@@ -41,6 +45,17 @@ void PaintBubbleGeneric(
fillBg(args.geometry);
return;
}
const auto &settings = AyuSettings::getInstance();
if (settings.removeMessageTail) {
if (bottomWithTailLeft == Corner::Tail) {
bottomWithTailLeft = Corner::Large;
}
if (bottomWithTailRight == Corner::Tail) {
bottomWithTailRight = Corner::Large;
}
}
const auto bottomLeft = (bottomWithTailLeft == Corner::Tail)
? Corner::None
: bottomWithTailLeft;
@@ -86,8 +86,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ayu/features/streamer_mode/streamer_mode.h"
#include "styles/style_ayu_icons.h"
#include "lang_auto.h"
#include "ayu/ui/settings/settings_ayu.h"
#include "ayu/ui/settings/settings_main.h"
namespace Window {
namespace {
@@ -624,7 +623,7 @@ void MainMenu::setupAccountsToggle() {
void MainMenu::setupSetEmojiStatus() {
_setEmojiStatus->overrideLinkClickHandler([=] {
_controller->showSettings(Settings::Ayu::Id());
_controller->showSettings(Settings::AyuMain::Id());
});
}
@@ -652,6 +651,7 @@ void MainMenu::setupMenu() {
std::move(descriptor));
};
if (!_controller->session().supportMode()) {
if (settings.showMyProfileInDrawer)
_menu->add(
CreateButtonWithIcon(
_menu,
@@ -663,12 +663,15 @@ void MainMenu::setupMenu() {
Info::Stories::Make(controller->session().user()));
});
if (settings.showBotsInDrawer)
SetupMenuBots(_menu, controller);
if (settings.showMyProfileInDrawer || settings.showBotsInDrawer)
_menu->add(
object_ptr<Ui::PlainShadow>(_menu),
{ 0, st::mainMenuSkip, 0, st::mainMenuSkip });
if (settings.showNewGroupInDrawer)
AddMyChannelsBox(addAction(
tr::lng_create_group_title(),
{ &st::menuIconGroups }
@@ -678,6 +681,7 @@ void MainMenu::setupMenu() {
}
});
if (settings.showNewChannelInDrawer)
AddMyChannelsBox(addAction(
tr::lng_create_channel_title(),
{ &st::menuIconChannel }
@@ -687,18 +691,21 @@ void MainMenu::setupMenu() {
}
});
if (settings.showContactsInDrawer)
addAction(
tr::lng_menu_contacts(),
{ &st::menuIconUserShow }
)->setClickedCallback([=] {
controller->show(PrepareContactsBox(controller));
});
if (settings.showCallsInDrawer)
addAction(
tr::lng_menu_calls(),
{ &st::menuIconPhone }
)->setClickedCallback([=] {
::Calls::ShowCallsBox(controller);
});
if (settings.showSavedMessagesInDrawer)
addAction(
tr::lng_saved_messages(),
{ &st::menuIconSavedMessages }
@@ -706,18 +713,16 @@ void MainMenu::setupMenu() {
controller->showPeerHistory(controller->session().user());
});
const auto &settings = AyuSettings::getInstance();
if (settings.showLReadToggleInDrawer) {
addAction(
tr::ayu_LReadMessages(),
{&st::ayuLReadMenuIcon}
)->setClickedCallback([=]
{
auto prev = settings.sendReadMessages;
const auto prev = settings.sendReadMessages;
AyuSettings::set_sendReadMessages(false);
auto chats = controller->session().data().chatsList();
const auto chats = controller->session().data().chatsList();
MarkAsReadChatList(chats);
AyuSettings::set_sendReadMessages(prev);
@@ -784,6 +789,8 @@ void MainMenu::setupMenu() {
controller->showSettings();
});
if (settings.showNightModeToggleInDrawer) {
_nightThemeToggle = addAction(
tr::lng_menu_night_mode(),
{ &st::menuIconNightMode }
@@ -813,30 +820,40 @@ void MainMenu::setupMenu() {
&_controller->window(),
toggle);
}, _nightThemeToggle->lifetime());
Core::App().settings().systemDarkModeValue(
) | rpl::start_with_next([=](std::optional<bool> darkMode) {
const auto darkModeEnabled
= Core::App().settings().systemDarkModeEnabled();
if (darkModeEnabled && darkMode.has_value()) {
_nightThemeSwitches.fire_copy(*darkMode);
}
}, _nightThemeToggle->lifetime());
}
if (settings.showGhostToggleInDrawer) {
_ghostModeToggle = addAction(
const auto ghostModeToggle = addAction(
tr::ayu_GhostModeToggle(),
{&st::ayuGhostIcon}
)->toggleOn(AyuSettings::get_ghostModeEnabledReactive());
_ghostModeToggle->toggledChanges(
ghostModeToggle->toggledChanges(
) | rpl::start_with_next(
[=](bool ghostMode)
{
AyuSettings::set_ghostModeEnabled(ghostMode);
AyuSettings::save();
},
_ghostModeToggle->lifetime());
ghostModeToggle->lifetime());
}
if (settings.showStreamerToggleInDrawer) {
_streamerModeToggle = addAction(
const auto streamerModeToggle = addAction(
tr::ayu_StreamerModeToggle(),
{&st::ayuStreamerModeMenuIcon}
)->toggleOn(rpl::single(AyuFeatures::StreamerMode::isEnabled()));
_streamerModeToggle->toggledChanges(
streamerModeToggle->toggledChanges(
) | rpl::start_with_next(
[=](bool enabled)
{
@@ -846,17 +863,8 @@ void MainMenu::setupMenu() {
AyuFeatures::StreamerMode::disable();
}
},
_streamerModeToggle->lifetime());
streamerModeToggle->lifetime());
}
Core::App().settings().systemDarkModeValue(
) | rpl::start_with_next([=](std::optional<bool> darkMode) {
const auto darkModeEnabled
= Core::App().settings().systemDarkModeEnabled();
if (darkModeEnabled && darkMode.has_value()) {
_nightThemeSwitches.fire_copy(*darkMode);
}
}, _nightThemeToggle->lifetime());
}
void MainMenu::resizeEvent(QResizeEvent *e) {
@@ -104,8 +104,6 @@ private:
not_null<Ui::FlatLabel*> _telegram;
not_null<Ui::FlatLabel*> _version;
QPointer<Ui::SettingsButton> _nightThemeToggle;
QPointer<Ui::SettingsButton> _ghostModeToggle;
QPointer<Ui::SettingsButton> _streamerModeToggle;
rpl::event_stream<bool> _nightThemeSwitches;
base::Timer _nightThemeSwitch;
base::unique_qptr<Ui::PopupMenu> _contextMenu;