mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
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:
@@ -14,7 +14,7 @@ class ChannelData;
|
||||
|
||||
namespace Info::Profile {
|
||||
class Badge;
|
||||
enum class BadgeType : uchar;
|
||||
enum class BadgeType : ushort;
|
||||
} // namespace Info::Profile
|
||||
|
||||
namespace Main {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user