diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 57ee25a5e..802847e2b 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -102,6 +102,8 @@ PRIVATE ayu/ayu_lang.cpp ayu/ayu_lang.h ayu/ayu_constants.h + ayu/utils/ayu_mapper.cpp + ayu/utils/ayu_mapper.h ayu/utils/telegram_helpers.cpp ayu/utils/telegram_helpers.h ayu/utils/windows_utils.cpp @@ -118,6 +120,12 @@ PRIVATE ayu/ui/settings/settings_ayu.h ayu/ui/context_menu/context_menu.cpp ayu/ui/context_menu/context_menu.h + ayu/ui/sections/edited/edited_log_inner.cpp + ayu/ui/sections/edited/edited_log_inner.h + ayu/ui/sections/edited/edited_log_item.cpp + ayu/ui/sections/edited/edited_log_item.h + ayu/ui/sections/edited/edited_log_section.cpp + ayu/ui/sections/edited/edited_log_section.h ayu/ui/boxes/voice_confirmation_box.cpp ayu/ui/boxes/voice_confirmation_box.h ayu/ui/boxes/message_history_box.cpp diff --git a/Telegram/SourceFiles/ayu/ayu_constants.h b/Telegram/SourceFiles/ayu/ayu_constants.h index 9db48d847..84243ac55 100644 --- a/Telegram/SourceFiles/ayu/ayu_constants.h +++ b/Telegram/SourceFiles/ayu/ayu_constants.h @@ -4,11 +4,13 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once -// https://github.com/AyuGram/AyuGram4A/blob/rewrite/TMessagesProj/src/main/java/com/radolyn/ayugram/AyuConstants.java +// https://github.com/AyuGram/AyuGram4AX/blob/rewrite/TMessagesProj/src/main/java/com/radolyn/ayugram/AyuConstants.java constexpr int DOCUMENT_TYPE_NONE = 0; + constexpr int DOCUMENT_TYPE_PHOTO = 1; + constexpr int DOCUMENT_TYPE_STICKER = 2; + constexpr int DOCUMENT_TYPE_FILE = 3; diff --git a/Telegram/SourceFiles/ayu/ayu_lang.cpp b/Telegram/SourceFiles/ayu/ayu_lang.cpp index 974c3f332..29af50cbb 100644 --- a/Telegram/SourceFiles/ayu/ayu_lang.cpp +++ b/Telegram/SourceFiles/ayu/ayu_lang.cpp @@ -12,7 +12,7 @@ #include "core/core_settings.h" #include "lang/lang_instance.h" -CustomLangPack* CustomLangPack::instance = nullptr; +CustomLangPack *CustomLangPack::instance = nullptr; CustomLangPack::CustomLangPack() = default; @@ -22,39 +22,35 @@ void CustomLangPack::initInstance() instance = new CustomLangPack; } -CustomLangPack* CustomLangPack::currentInstance() +CustomLangPack *CustomLangPack::currentInstance() { return instance; } -void CustomLangPack::fetchCustomLangPack(const QString& langPackId, const QString& langPackBaseId) +void CustomLangPack::fetchCustomLangPack(const QString &langPackId, const QString &langPackBaseId) { LOG(("Current Language pack ID: %1, Base ID: %2").arg(langPackId, langPackBaseId)); auto finalLangPackId = langPackId; - if (finalLangPackId == qsl("pt-br")) - { + if (finalLangPackId == qsl("pt-br")) { // meh finalLangPackId = qsl("pt"); } const auto proxy = Core::App().settings().proxy().isEnabled() - ? Core::App().settings().proxy().selected() - : MTP::ProxyData(); - if (proxy.type == MTP::ProxyData::Type::Socks5 || proxy.type == MTP::ProxyData::Type::Http) - { + ? Core::App().settings().proxy().selected() + : MTP::ProxyData(); + if (proxy.type == MTP::ProxyData::Type::Socks5 || proxy.type == MTP::ProxyData::Type::Http) { QNetworkProxy LocaleProxy = ToNetworkProxy(ToDirectIpProxy(proxy)); networkManager.setProxy(LocaleProxy); } QUrl url; - if (!finalLangPackId.isEmpty() && !langPackBaseId.isEmpty() && !needFallback) - { + if (!finalLangPackId.isEmpty() && !langPackBaseId.isEmpty() && !needFallback) { url.setUrl(qsl("https://raw.githubusercontent.com/AyuGram/Languages/l10n_main/values/langs/%1/Shared.json").arg( finalLangPackId)); } - else - { + else { url.setUrl(qsl("https://raw.githubusercontent.com/AyuGram/Languages/l10n_main/values/langs/%1/Shared.json").arg( needFallback ? langPackBaseId : finalLangPackId)); } @@ -73,24 +69,20 @@ void CustomLangPack::fetchFinished() QString langPackId = Lang::GetInstance().id(); auto statusCode = _chkReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (statusCode == 404 && !langPackId.isEmpty() && !langPackBaseId.isEmpty() && !needFallback) - { + if (statusCode == 404 && !langPackId.isEmpty() && !langPackBaseId.isEmpty() && !needFallback) { LOG(("AyuGram Language pack not found! Fallback to main language: %1...").arg(langPackBaseId)); needFallback = true; _chkReply->disconnect(); fetchCustomLangPack("", langPackBaseId); } - else - { + else { QByteArray result = _chkReply->readAll().trimmed(); QJsonParseError error{}; QJsonDocument str = QJsonDocument::fromJson(result, &error); - if (error.error == QJsonParseError::NoError) - { + if (error.error == QJsonParseError::NoError) { parseLangFile(str); } - else - { + else { LOG(("Incorrect JSON File. Fallback to default language: English...")); loadDefaultLangFile(); } @@ -103,20 +95,17 @@ void CustomLangPack::fetchError(QNetworkReply::NetworkError e) { LOG(("Network error: %1").arg(e)); - if (e == QNetworkReply::NetworkError::ContentNotFoundError) - { + if (e == QNetworkReply::NetworkError::ContentNotFoundError) { QString langPackBaseId = Lang::GetInstance().baseId(); QString langPackId = Lang::GetInstance().id(); - if (!langPackId.isEmpty() && !langPackBaseId.isEmpty() && !needFallback) - { + if (!langPackId.isEmpty() && !langPackBaseId.isEmpty() && !needFallback) { LOG(("AyuGram Language pack not found! Fallback to main language: %1...").arg(langPackBaseId)); needFallback = true; _chkReply->disconnect(); fetchCustomLangPack("", langPackBaseId); } - else - { + else { LOG(("AyuGram Language pack not found! Fallback to default language: English...")); loadDefaultLangFile(); _chkReply = nullptr; @@ -127,12 +116,10 @@ void CustomLangPack::fetchError(QNetworkReply::NetworkError e) void CustomLangPack::loadDefaultLangFile() { QFile file(":/localization/en.json"); - if (file.open(QIODevice::ReadOnly)) - { + if (file.open(QIODevice::ReadOnly)) { QJsonDocument str = QJsonDocument::fromJson(file.readAll()); QJsonObject json = str.object(); - for (const QString& key : json.keys()) - { + for (const QString &key : json.keys()) { Lang::GetInstance().applyValue(key.toUtf8(), json.value(key).toString().toUtf8()); } Lang::GetInstance().updatePluralRules(); @@ -143,15 +130,13 @@ void CustomLangPack::loadDefaultLangFile() void CustomLangPack::parseLangFile(QJsonDocument str) { QJsonObject json = str.object(); - for (const QString& brokenKey : json.keys()) - { + for (const QString &brokenKey : json.keys()) { auto key = qsl("ayu_") + brokenKey; auto val = json.value(brokenKey).toString().replace(qsl("&"), qsl("&")).toUtf8(); Lang::GetInstance().resetValue(key.toUtf8()); Lang::GetInstance().applyValue(key.toUtf8(), val); - if (key.contains("#other")) - { + if (key.contains("#other")) { Lang::GetInstance().resetValue(key.toUtf8().replace("#other", "#few")); Lang::GetInstance().resetValue(key.toUtf8().replace("#other", "#few")); Lang::GetInstance().applyValue(key.toUtf8().replace("#other", "#few"), val); diff --git a/Telegram/SourceFiles/ayu/ayu_lang.h b/Telegram/SourceFiles/ayu/ayu_lang.h index 6705d4843..329ba28ce 100644 --- a/Telegram/SourceFiles/ayu/ayu_lang.h +++ b/Telegram/SourceFiles/ayu/ayu_lang.h @@ -4,7 +4,6 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once #include @@ -12,18 +11,18 @@ class CustomLangPack : public QObject { - Q_OBJECT +Q_OBJECT Q_DISABLE_COPY(CustomLangPack) public: - static CustomLangPack* currentInstance(); + static CustomLangPack *currentInstance(); static void initInstance(); - static CustomLangPack* instance; + static CustomLangPack *instance; - void fetchCustomLangPack(const QString& langPackId, const QString& langPackBaseId); + void fetchCustomLangPack(const QString &langPackId, const QString &langPackBaseId); void loadDefaultLangFile(); @@ -40,6 +39,6 @@ private: ~CustomLangPack() override = default; QNetworkAccessManager networkManager; - QNetworkReply* _chkReply = nullptr; + QNetworkReply *_chkReply = nullptr; bool needFallback = false; }; diff --git a/Telegram/SourceFiles/ayu/ayu_settings.cpp b/Telegram/SourceFiles/ayu/ayu_settings.cpp index e276b9a61..ac9f58372 100644 --- a/Telegram/SourceFiles/ayu/ayu_settings.cpp +++ b/Telegram/SourceFiles/ayu/ayu_settings.cpp @@ -14,301 +14,315 @@ using json = nlohmann::json; namespace AyuSettings { - const QString filename = "tdata/ayu_settings.json"; - std::optional settings = std::nullopt; - rpl::variable sendReadMessagesReactive; - rpl::variable sendReadStoriesReactive; - rpl::variable sendOnlinePacketsReactive; - rpl::variable sendUploadProgressReactive; - rpl::variable sendOfflinePacketAfterOnlineReactive; +const QString filename = "tdata/ayu_settings.json"; - rpl::variable deletedMarkReactive; - rpl::variable editedMarkReactive; - rpl::variable showPeerIdReactive; +std::optional settings = std::nullopt; - rpl::variable ghostModeEnabled; +rpl::variable sendReadMessagesReactive; - rpl::lifetime lifetime = rpl::lifetime(); +rpl::variable sendReadStoriesReactive; - bool ghostModeEnabled_util(AyuGramSettings& settingsUtil) - { - return - !settingsUtil.sendReadMessages +rpl::variable sendOnlinePacketsReactive; + +rpl::variable sendUploadProgressReactive; + +rpl::variable sendOfflinePacketAfterOnlineReactive; + +rpl::variable deletedMarkReactive; + +rpl::variable editedMarkReactive; + +rpl::variable showPeerIdReactive; + +rpl::variable ghostModeEnabled; + +rpl::lifetime lifetime = rpl::lifetime(); + +bool ghostModeEnabled_util(AyuGramSettings &settingsUtil) +{ + return + !settingsUtil.sendReadMessages && !settingsUtil.sendReadStories && !settingsUtil.sendOnlinePackets && !settingsUtil.sendUploadProgress && settingsUtil.sendOfflinePacketAfterOnline; - } - - void initialize() - { - if (settings.has_value()) - { - return; - } - - settings = AyuGramSettings(); - - sendReadMessagesReactive.value() | rpl::filter([=](bool val) - { - return (val != settings->sendReadMessages); - }) | start_with_next([=](bool val) - { - ghostModeEnabled = ghostModeEnabled_util(settings.value()); - }, lifetime); - // .. - sendReadStoriesReactive.value() | rpl::filter([=](bool val) - { - return (val != settings->sendReadStories); - }) | start_with_next([=](bool val) - { - ghostModeEnabled = ghostModeEnabled_util(settings.value()); - }, lifetime); - // .. - sendOnlinePacketsReactive.value() | rpl::filter([=](bool val) - { - return (val != settings->sendOnlinePackets); - }) | start_with_next([=](bool val) - { - ghostModeEnabled = ghostModeEnabled_util(settings.value()); - }, lifetime); - // .. - sendUploadProgressReactive.value() | rpl::filter([=](bool val) - { - return (val != settings->sendUploadProgress); - }) | start_with_next([=](bool val) - { - ghostModeEnabled = ghostModeEnabled_util(settings.value()); - }, lifetime); - // .. - sendOfflinePacketAfterOnlineReactive.value() | rpl::filter([=](bool val) - { - return (val != settings->sendOfflinePacketAfterOnline); - }) | start_with_next([=](bool val) - { - ghostModeEnabled = ghostModeEnabled_util(settings.value()); - }, lifetime); - } - - void postinitialize() - { - sendReadMessagesReactive = settings->sendReadMessages; - sendReadStoriesReactive = settings->sendReadStories; - sendUploadProgressReactive = settings->sendUploadProgress; - sendOfflinePacketAfterOnlineReactive = settings->sendOfflinePacketAfterOnline; - sendOnlinePacketsReactive = settings->sendOnlinePackets; - - deletedMarkReactive = settings->deletedMark; - editedMarkReactive = settings->editedMark; - showPeerIdReactive = settings->showPeerId; - - ghostModeEnabled = ghostModeEnabled_util(settings.value()); - } - - AyuGramSettings& getInstance() - { - initialize(); - return settings.value(); - } - - void load() - { - QFile file(filename); - if (!file.exists()) - { - return; - } - file.open(QIODevice::ReadOnly); - QByteArray data = file.readAll(); - file.close(); - - initialize(); - json p = json::parse(data); - try - { - settings = p.get(); - } - catch (...) - { - LOG(("AyuGramSettings: failed to parse settings file")); - } - postinitialize(); - } - - void save() - { - initialize(); - - json p = settings.value(); - - QFile file(filename); - file.open(QIODevice::WriteOnly); - file.write(p.dump().c_str()); - file.close(); - - postinitialize(); - } - - void AyuGramSettings::set_sendReadMessages(bool val) - { - sendReadMessages = val; - sendReadMessagesReactive = val; - } - - void AyuGramSettings::set_sendReadStories(bool val) - { - sendReadStories = val; - sendReadStoriesReactive = val; - } - - void AyuGramSettings::set_sendOnlinePackets(bool val) - { - sendOnlinePackets = val; - sendOnlinePacketsReactive = val; - } - - void AyuGramSettings::set_sendUploadProgress(bool val) - { - sendUploadProgress = val; - sendUploadProgressReactive = val; - } - - void AyuGramSettings::set_sendOfflinePacketAfterOnline(bool val) - { - sendOfflinePacketAfterOnline = val; - sendOfflinePacketAfterOnlineReactive = val; - } - - void AyuGramSettings::set_ghostModeEnabled(bool val) - { - set_sendReadMessages(!val); - set_sendReadStories(!val); - set_sendOnlinePackets(!val); - set_sendUploadProgress(!val); - set_sendOfflinePacketAfterOnline(val); - } - - void AyuGramSettings::set_markReadAfterSend(bool val) - { - markReadAfterSend = val; - } - - void AyuGramSettings::set_useScheduledMessages(bool val) - { - useScheduledMessages = val; - } - - void AyuGramSettings::set_keepDeletedMessages(bool val) - { - saveDeletedMessages = val; - } - - void AyuGramSettings::set_keepMessagesHistory(bool val) - { - saveMessagesHistory = val; - } - - void AyuGramSettings::set_disableAds(bool val) - { - disableAds = val; - } - - void AyuGramSettings::set_disableStories(bool val) - { - disableStories = val; - } - - void AyuGramSettings::set_localPremium(bool val) - { - localPremium = val; - } - - void AyuGramSettings::set_copyUsernameAsLink(bool val) - { - copyUsernameAsLink = val; - } - - void AyuGramSettings::set_appIcon(QString val) - { - appIcon = std::move(val); - } - - void AyuGramSettings::set_deletedMark(QString val) - { - deletedMark = std::move(val); - deletedMarkReactive = deletedMark; - } - - void AyuGramSettings::set_editedMark(QString val) - { - editedMark = std::move(val); - editedMarkReactive = editedMark; - } - - void AyuGramSettings::set_recentStickersCount(int val) - { - recentStickersCount = val; - } - - void AyuGramSettings::set_showGhostToggleInDrawer(bool val) - { - showGhostToggleInDrawer = val; - } - - void AyuGramSettings::set_showPeerId(int val) - { - showPeerId = val; - showPeerIdReactive = val; - } - - void AyuGramSettings::set_showMessageSeconds(bool val) - { - showMessageSeconds = val; - } - - void AyuGramSettings::set_hideAllChatsFolder(bool val) - { - hideAllChatsFolder = val; - } - - void AyuGramSettings::set_stickerConfirmation(bool val) - { - stickerConfirmation = val; - } - - void AyuGramSettings::set_gifConfirmation(bool val) - { - gifConfirmation = val; - } - - void AyuGramSettings::set_voiceConfirmation(bool val) - { - voiceConfirmation = val; - } - - bool get_ghostModeEnabled() - { - return ghostModeEnabled.current(); - } - - rpl::producer get_deletedMarkReactive() - { - return deletedMarkReactive.value(); - } - - rpl::producer get_editedMarkReactive() - { - return editedMarkReactive.value(); - } - - rpl::producer get_showPeerIdReactive() - { - return showPeerIdReactive.value(); - } - - rpl::producer get_ghostModeEnabledReactive() - { - return ghostModeEnabled.value(); - } +} + +void initialize() +{ + if (settings.has_value()) { + return; + } + + settings = AyuGramSettings(); + + sendReadMessagesReactive.value() | rpl::filter([=](bool val) + { + return (val != settings->sendReadMessages); + }) | start_with_next([=](bool val) + { + ghostModeEnabled = + ghostModeEnabled_util(settings.value()); + }, lifetime); + // .. + sendReadStoriesReactive.value() | rpl::filter([=](bool val) + { + return (val != settings->sendReadStories); + }) | start_with_next([=](bool val) + { + ghostModeEnabled = + ghostModeEnabled_util(settings.value()); + }, lifetime); + // .. + sendOnlinePacketsReactive.value() | rpl::filter([=](bool val) + { + return (val != settings->sendOnlinePackets); + }) | start_with_next([=](bool val) + { + ghostModeEnabled = + ghostModeEnabled_util(settings + .value()); + }, lifetime); + // .. + sendUploadProgressReactive.value() | rpl::filter([=](bool val) + { + return (val != settings->sendUploadProgress); + }) | start_with_next([=](bool val) + { + ghostModeEnabled = + ghostModeEnabled_util(settings + .value()); + }, lifetime); + // .. + sendOfflinePacketAfterOnlineReactive.value() | rpl::filter([=](bool val) + { + return (val + != settings->sendOfflinePacketAfterOnline); + }) | start_with_next([=](bool val) + { + ghostModeEnabled = + ghostModeEnabled_util( + settings.value()); + }, lifetime); +} + +void postinitialize() +{ + sendReadMessagesReactive = settings->sendReadMessages; + sendReadStoriesReactive = settings->sendReadStories; + sendUploadProgressReactive = settings->sendUploadProgress; + sendOfflinePacketAfterOnlineReactive = settings->sendOfflinePacketAfterOnline; + sendOnlinePacketsReactive = settings->sendOnlinePackets; + + deletedMarkReactive = settings->deletedMark; + editedMarkReactive = settings->editedMark; + showPeerIdReactive = settings->showPeerId; + + ghostModeEnabled = ghostModeEnabled_util(settings.value()); +} + +AyuGramSettings &getInstance() +{ + initialize(); + return settings.value(); +} + +void load() +{ + QFile file(filename); + if (!file.exists()) { + return; + } + file.open(QIODevice::ReadOnly); + QByteArray data = file.readAll(); + file.close(); + + initialize(); + json p = json::parse(data); + try { + settings = p.get(); + } + catch (...) { + LOG(("AyuGramSettings: failed to parse settings file")); + } + postinitialize(); +} + +void save() +{ + initialize(); + + json p = settings.value(); + + QFile file(filename); + file.open(QIODevice::WriteOnly); + file.write(p.dump().c_str()); + file.close(); + + postinitialize(); +} + +void AyuGramSettings::set_sendReadMessages(bool val) +{ + sendReadMessages = val; + sendReadMessagesReactive = val; +} + +void AyuGramSettings::set_sendReadStories(bool val) +{ + sendReadStories = val; + sendReadStoriesReactive = val; +} + +void AyuGramSettings::set_sendOnlinePackets(bool val) +{ + sendOnlinePackets = val; + sendOnlinePacketsReactive = val; +} + +void AyuGramSettings::set_sendUploadProgress(bool val) +{ + sendUploadProgress = val; + sendUploadProgressReactive = val; +} + +void AyuGramSettings::set_sendOfflinePacketAfterOnline(bool val) +{ + sendOfflinePacketAfterOnline = val; + sendOfflinePacketAfterOnlineReactive = val; +} + +void AyuGramSettings::set_ghostModeEnabled(bool val) +{ + set_sendReadMessages(!val); + set_sendReadStories(!val); + set_sendOnlinePackets(!val); + set_sendUploadProgress(!val); + set_sendOfflinePacketAfterOnline(val); +} + +void AyuGramSettings::set_markReadAfterSend(bool val) +{ + markReadAfterSend = val; +} + +void AyuGramSettings::set_useScheduledMessages(bool val) +{ + useScheduledMessages = val; +} + +void AyuGramSettings::set_keepDeletedMessages(bool val) +{ + saveDeletedMessages = val; +} + +void AyuGramSettings::set_keepMessagesHistory(bool val) +{ + saveMessagesHistory = val; +} + +void AyuGramSettings::set_disableAds(bool val) +{ + disableAds = val; +} + +void AyuGramSettings::set_disableStories(bool val) +{ + disableStories = val; +} + +void AyuGramSettings::set_localPremium(bool val) +{ + localPremium = val; +} + +void AyuGramSettings::set_copyUsernameAsLink(bool val) +{ + copyUsernameAsLink = val; +} + +void AyuGramSettings::set_appIcon(QString val) +{ + appIcon = std::move(val); +} + +void AyuGramSettings::set_deletedMark(QString val) +{ + deletedMark = std::move(val); + deletedMarkReactive = deletedMark; +} + +void AyuGramSettings::set_editedMark(QString val) +{ + editedMark = std::move(val); + editedMarkReactive = editedMark; +} + +void AyuGramSettings::set_recentStickersCount(int val) +{ + recentStickersCount = val; +} + +void AyuGramSettings::set_showGhostToggleInDrawer(bool val) +{ + showGhostToggleInDrawer = val; +} + +void AyuGramSettings::set_showPeerId(int val) +{ + showPeerId = val; + showPeerIdReactive = val; +} + +void AyuGramSettings::set_showMessageSeconds(bool val) +{ + showMessageSeconds = val; +} + +void AyuGramSettings::set_hideAllChatsFolder(bool val) +{ + hideAllChatsFolder = val; +} + +void AyuGramSettings::set_stickerConfirmation(bool val) +{ + stickerConfirmation = val; +} + +void AyuGramSettings::set_gifConfirmation(bool val) +{ + gifConfirmation = val; +} + +void AyuGramSettings::set_voiceConfirmation(bool val) +{ + voiceConfirmation = val; +} + +bool get_ghostModeEnabled() +{ + return ghostModeEnabled.current(); +} + +rpl::producer get_deletedMarkReactive() +{ + return deletedMarkReactive.value(); +} + +rpl::producer get_editedMarkReactive() +{ + return editedMarkReactive.value(); +} + +rpl::producer get_showPeerIdReactive() +{ + return showPeerIdReactive.value(); +} + +rpl::producer get_ghostModeEnabledReactive() +{ + return ghostModeEnabled.value(); +} + } diff --git a/Telegram/SourceFiles/ayu/ayu_settings.h b/Telegram/SourceFiles/ayu/ayu_settings.h index 8ec8f5301..cd95ee6f8 100644 --- a/Telegram/SourceFiles/ayu/ayu_settings.h +++ b/Telegram/SourceFiles/ayu/ayu_settings.h @@ -4,7 +4,6 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once #include "lang_auto.h" @@ -14,176 +13,180 @@ namespace AyuSettings { - const auto DEFAULT_ICON = QString("default"); - const auto ALT_ICON = QString("alt"); - const auto NOTHING_ICON = QString("nothing"); - class AyuGramSettings +const auto DEFAULT_ICON = QString("default"); + +const auto ALT_ICON = QString("alt"); + +const auto NOTHING_ICON = QString("nothing"); + +class AyuGramSettings +{ +public: + AyuGramSettings() { - public: - AyuGramSettings() - { - // ~ Ghost essentials - sendReadMessages = true; - sendReadStories = true; - sendOnlinePackets = true; - sendUploadProgress = true; - sendOfflinePacketAfterOnline = false; + // ~ Ghost essentials + sendReadMessages = true; + sendReadStories = true; + sendOnlinePackets = true; + sendUploadProgress = true; + sendOfflinePacketAfterOnline = false; - markReadAfterSend = true; - useScheduledMessages = false; + markReadAfterSend = true; + useScheduledMessages = false; - // ~ Message edits & deletion history - saveDeletedMessages = true; - saveMessagesHistory = true; + // ~ Message edits & deletion history + saveDeletedMessages = true; + saveMessagesHistory = true; - // ~ QoL toggles - disableAds = true; - disableStories = false; - localPremium = false; - copyUsernameAsLink = true; + // ~ QoL toggles + disableAds = true; + disableStories = false; + localPremium = false; + copyUsernameAsLink = true; - // ~ Customization - appIcon = DEFAULT_ICON; - deletedMark = "🧹"; - editedMark = tr::lng_edited(tr::now); - recentStickersCount = 20; - showGhostToggleInDrawer = true; + // ~ Customization + appIcon = DEFAULT_ICON; + deletedMark = "🧹"; + editedMark = tr::lng_edited(tr::now); + recentStickersCount = 20; + showGhostToggleInDrawer = true; - /* - * showPeerId = 0 means no ID shown - * showPeerId = 1 means ID shown as for Telegram API devs - * showPeerId = 2 means ID shown as for Bot API devs (-100) - */ - showPeerId = 2; + /* + * showPeerId = 0 means no ID shown + * showPeerId = 1 means ID shown as for Telegram API devs + * showPeerId = 2 means ID shown as for Bot API devs (-100) + */ + showPeerId = 2; - hideAllChatsFolder = false; - showMessageSeconds = false; + hideAllChatsFolder = false; + showMessageSeconds = false; - // ~ Confirmations - stickerConfirmation = false; - gifConfirmation = false; - voiceConfirmation = false; - } + // ~ Confirmations + stickerConfirmation = false; + gifConfirmation = false; + voiceConfirmation = false; + } - bool sendReadMessages; - bool sendReadStories; - bool sendOnlinePackets; - bool sendUploadProgress; - bool sendOfflinePacketAfterOnline; - bool markReadAfterSend; - bool useScheduledMessages; - bool saveDeletedMessages; - bool saveMessagesHistory; - bool disableAds; - bool disableStories; - bool localPremium; - bool copyUsernameAsLink; - QString appIcon; - QString deletedMark; - QString editedMark; - int recentStickersCount; - bool showGhostToggleInDrawer; - int showPeerId; - bool hideAllChatsFolder; - bool showMessageSeconds; - bool stickerConfirmation; - bool gifConfirmation; - bool voiceConfirmation; + bool sendReadMessages; + bool sendReadStories; + bool sendOnlinePackets; + bool sendUploadProgress; + bool sendOfflinePacketAfterOnline; + bool markReadAfterSend; + bool useScheduledMessages; + bool saveDeletedMessages; + bool saveMessagesHistory; + bool disableAds; + bool disableStories; + bool localPremium; + bool copyUsernameAsLink; + QString appIcon; + QString deletedMark; + QString editedMark; + int recentStickersCount; + bool showGhostToggleInDrawer; + int showPeerId; + bool hideAllChatsFolder; + bool showMessageSeconds; + bool stickerConfirmation; + bool gifConfirmation; + bool voiceConfirmation; - public: - void set_sendReadMessages(bool val); +public: + void set_sendReadMessages(bool val); - void set_sendReadStories(bool val); + void set_sendReadStories(bool val); - void set_sendOnlinePackets(bool val); + void set_sendOnlinePackets(bool val); - void set_sendUploadProgress(bool val); + void set_sendUploadProgress(bool val); - void set_sendOfflinePacketAfterOnline(bool val); + void set_sendOfflinePacketAfterOnline(bool val); - void set_ghostModeEnabled(bool val); + void set_ghostModeEnabled(bool val); - void set_markReadAfterSend(bool val); + void set_markReadAfterSend(bool val); - void set_useScheduledMessages(bool val); + void set_useScheduledMessages(bool val); - void set_keepDeletedMessages(bool val); + void set_keepDeletedMessages(bool val); - void set_keepMessagesHistory(bool val); + void set_keepMessagesHistory(bool val); - void set_disableAds(bool val); + void set_disableAds(bool val); - void set_disableStories(bool val); + void set_disableStories(bool val); - void set_localPremium(bool val); + void set_localPremium(bool val); - void set_copyUsernameAsLink(bool val); + void set_copyUsernameAsLink(bool val); - void set_appIcon(QString val); + void set_appIcon(QString val); - void set_deletedMark(QString val); + void set_deletedMark(QString val); - void set_editedMark(QString val); + void set_editedMark(QString val); - void set_recentStickersCount(int val); + void set_recentStickersCount(int val); - void set_showGhostToggleInDrawer(bool val); + void set_showGhostToggleInDrawer(bool val); - void set_showPeerId(int val); + void set_showPeerId(int val); - void set_showMessageSeconds(bool val); + void set_showMessageSeconds(bool val); - void set_hideAllChatsFolder(bool val); + void set_hideAllChatsFolder(bool val); - void set_stickerConfirmation(bool val); + void set_stickerConfirmation(bool val); - void set_gifConfirmation(bool val); + void set_gifConfirmation(bool val); - void set_voiceConfirmation(bool val); - }; + void set_voiceConfirmation(bool val); +}; - NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( - AyuGramSettings, - sendReadMessages, - sendReadStories, - sendOnlinePackets, - sendUploadProgress, - sendOfflinePacketAfterOnline, - markReadAfterSend, - useScheduledMessages, - saveDeletedMessages, - saveMessagesHistory, - disableAds, - disableStories, - localPremium, - copyUsernameAsLink, - appIcon, - deletedMark, - editedMark, - recentStickersCount, - showGhostToggleInDrawer, - showPeerId, - showMessageSeconds, - hideAllChatsFolder, - stickerConfirmation, - gifConfirmation, - voiceConfirmation - ); +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( + AyuGramSettings, + sendReadMessages, + sendReadStories, + sendOnlinePackets, + sendUploadProgress, + sendOfflinePacketAfterOnline, + markReadAfterSend, + useScheduledMessages, + saveDeletedMessages, + saveMessagesHistory, + disableAds, + disableStories, + localPremium, + copyUsernameAsLink, + appIcon, + deletedMark, + editedMark, + recentStickersCount, + showGhostToggleInDrawer, + showPeerId, + showMessageSeconds, + hideAllChatsFolder, + stickerConfirmation, + gifConfirmation, + voiceConfirmation +); - AyuGramSettings& getInstance(); +AyuGramSettings &getInstance(); - void load(); +void load(); - void save(); +void save(); - rpl::producer get_deletedMarkReactive(); +rpl::producer get_deletedMarkReactive(); - rpl::producer get_editedMarkReactive(); +rpl::producer get_editedMarkReactive(); - rpl::producer get_showPeerIdReactive(); +rpl::producer get_showPeerIdReactive(); - bool get_ghostModeEnabled(); +bool get_ghostModeEnabled(); + +rpl::producer get_ghostModeEnabledReactive(); - rpl::producer get_ghostModeEnabledReactive(); } diff --git a/Telegram/SourceFiles/ayu/ayu_state.cpp b/Telegram/SourceFiles/ayu/ayu_state.cpp index 09105ac1b..bf3bb20d4 100644 --- a/Telegram/SourceFiles/ayu/ayu_state.cpp +++ b/Telegram/SourceFiles/ayu/ayu_state.cpp @@ -9,15 +9,17 @@ namespace AyuState { - void setAllowSendReadPacket(bool val, int resetAfter) - { - allowSendReadPacket.val = val; - allowSendReadPacket.resetAfter = resetAfter; - } - bool getAllowSendPacket() - { - auto settings = &AyuSettings::getInstance(); - return settings->sendReadMessages || processVariable(allowSendReadPacket); - } +void setAllowSendReadPacket(bool val, int resetAfter) +{ + allowSendReadPacket.val = val; + allowSendReadPacket.resetAfter = resetAfter; +} + +bool getAllowSendPacket() +{ + auto settings = &AyuSettings::getInstance(); + return settings->sendReadMessages || processVariable(allowSendReadPacket); +} + } diff --git a/Telegram/SourceFiles/ayu/ayu_state.h b/Telegram/SourceFiles/ayu/ayu_state.h index 74ac2f7de..44a6a0e71 100644 --- a/Telegram/SourceFiles/ayu/ayu_state.h +++ b/Telegram/SourceFiles/ayu/ayu_state.h @@ -4,44 +4,45 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once #include "ayu_settings.h" namespace AyuState { - namespace - { - class AyuStateVariable - { - public: - bool val; - int resetAfter; - }; - AyuStateVariable allowSendReadPacket; +namespace +{ - bool processVariable(AyuStateVariable& variable) - { - if (variable.resetAfter == -1) - { - return variable.val; - } +class AyuStateVariable +{ +public: + bool val; + int resetAfter; +}; - variable.resetAfter -= 1; - auto val = variable.val; +AyuStateVariable allowSendReadPacket; - if (variable.resetAfter == 0) - { - variable.val = false; - } - - return val; - } +bool processVariable(AyuStateVariable &variable) +{ + if (variable.resetAfter == -1) { + return variable.val; } - void setAllowSendReadPacket(bool val, int resetAfter = 1); + variable.resetAfter -= 1; + auto val = variable.val; + + if (variable.resetAfter == 0) { + variable.val = false; + } + + return val; +} + +} + +void setAllowSendReadPacket(bool val, int resetAfter = 1); + +bool getAllowSendPacket(); - bool getAllowSendPacket(); } diff --git a/Telegram/SourceFiles/ayu/database/ayu_database.cpp b/Telegram/SourceFiles/ayu/database/ayu_database.cpp index 9b603f026..cd9424b72 100644 --- a/Telegram/SourceFiles/ayu/database/ayu_database.cpp +++ b/Telegram/SourceFiles/ayu/database/ayu_database.cpp @@ -9,102 +9,127 @@ #include "entities.h" #include "ayu/libs/sqlite/sqlite_orm.h" -#include "ayu/utils/telegram_helpers.h" using namespace sqlite_orm; -auto storage = make_storage( - "ayugram.db", - make_table( - "editedmessage", - make_column("userId", &EditedMessage::get_user_id, &EditedMessage::set_user_id), - make_column("dialogId", &EditedMessage::get_dialog_id, &EditedMessage::set_dialog_id), - make_column("groupedId", &EditedMessage::get_grouped_id, &EditedMessage::set_grouped_id), - make_column("peerId", &EditedMessage::get_peer_id, &EditedMessage::set_peer_id), - make_column("fromId", &EditedMessage::get_from_id, &EditedMessage::set_from_id), - make_column("topicId", &EditedMessage::get_topic_id, &EditedMessage::set_topic_id), - make_column("messageId", &EditedMessage::get_message_id, &EditedMessage::set_message_id), - make_column("date", &EditedMessage::get_date, &EditedMessage::set_date), - make_column("flags", &EditedMessage::get_flags, &EditedMessage::set_flags), - make_column("editDate", &EditedMessage::get_edit_date, &EditedMessage::set_edit_date), - make_column("editHide", &EditedMessage::is_edit_hide, &EditedMessage::set_edit_hide), - make_column("out", &EditedMessage::is_out, &EditedMessage::set_out), - make_column("entityCreateDate", &EditedMessage::get_entity_create_date, &EditedMessage::set_entity_create_date), - make_column("text", &EditedMessage::get_text, &EditedMessage::set_text), - make_column("textEntities", &EditedMessage::get_text_entities, &EditedMessage::set_text_entities), - make_column("mediaPath", &EditedMessage::get_media_path, &EditedMessage::set_media_path), - make_column("documentType", &EditedMessage::get_document_type, &EditedMessage::set_document_type), - make_column("documentSerialized", &EditedMessage::get_document_serialized, - &EditedMessage::set_document_serialized), - make_column("thumbsSerialized", &EditedMessage::get_thumbs_serialized, &EditedMessage::set_thumbs_serialized), - make_column("documentAttributesSerialized", &EditedMessage::get_document_attributes_serialized, - &EditedMessage::set_document_attributes_serialized), - make_column("mimeType", &EditedMessage::get_mime_type, &EditedMessage::set_mime_type) - ), - make_table( - "deletedmessage", - make_column("userId", &DeletedMessage::get_user_id, &DeletedMessage::set_user_id), - make_column("dialogId", &DeletedMessage::get_dialog_id, &DeletedMessage::set_dialog_id), - make_column("groupedId", &DeletedMessage::get_grouped_id, &DeletedMessage::set_grouped_id), - make_column("peerId", &DeletedMessage::get_peer_id, &DeletedMessage::set_peer_id), - make_column("fromId", &DeletedMessage::get_from_id, &DeletedMessage::set_from_id), - make_column("topicId", &DeletedMessage::get_topic_id, &DeletedMessage::set_topic_id), - make_column("messageId", &DeletedMessage::get_message_id, &DeletedMessage::set_message_id), - make_column("date", &DeletedMessage::get_date, &DeletedMessage::set_date), - make_column("flags", &DeletedMessage::get_flags, &DeletedMessage::set_flags), - make_column("editDate", &DeletedMessage::get_edit_date, &DeletedMessage::set_edit_date), - make_column("editHide", &DeletedMessage::is_edit_hide, &DeletedMessage::set_edit_hide), - make_column("out", &DeletedMessage::is_out, &DeletedMessage::set_out), - make_column("entityCreateDate", &DeletedMessage::get_entity_create_date, - &DeletedMessage::set_entity_create_date), - make_column("text", &DeletedMessage::get_text, &DeletedMessage::set_text), - make_column("textEntities", &DeletedMessage::get_text_entities, &DeletedMessage::set_text_entities), - make_column("mediaPath", &DeletedMessage::get_media_path, &DeletedMessage::set_media_path), - make_column("documentType", &DeletedMessage::get_document_type, &DeletedMessage::set_document_type), - make_column("documentSerialized", &DeletedMessage::get_document_serialized, - &DeletedMessage::set_document_serialized), - make_column("thumbsSerialized", &DeletedMessage::get_thumbs_serialized, &DeletedMessage::set_thumbs_serialized), - make_column("documentAttributesSerialized", &DeletedMessage::get_document_attributes_serialized, - &DeletedMessage::set_document_attributes_serialized), - make_column("mimeType", &DeletedMessage::get_mime_type, &DeletedMessage::set_mime_type) - ) + +auto storage = make_storage("ayugram.db", + make_table("DeletedMessage", + make_column("userId", &DeletedMessage::userId), + make_column("dialogId", &DeletedMessage::dialogId), + make_column("groupedId", &DeletedMessage::groupedId), + make_column("peerId", &DeletedMessage::peerId), + make_column("fromId", &DeletedMessage::fromId), + make_column("topicId", &DeletedMessage::topicId), + make_column("messageId", &DeletedMessage::messageId), + make_column("date", &DeletedMessage::date), + make_column("flags", &DeletedMessage::flags), + make_column("editDate", &DeletedMessage::editDate), + make_column("views", &DeletedMessage::views), + make_column("fwdFlags", &DeletedMessage::fwdFlags), + make_column("fwdFromId", &DeletedMessage::fwdFromId), + make_column("fwdName", &DeletedMessage::fwdName), + make_column("fwdDate", &DeletedMessage::fwdDate), + make_column("fwdPostAuthor", &DeletedMessage::fwdPostAuthor), + make_column("replyFlags", &DeletedMessage::replyFlags), + make_column("replyMessageId", &DeletedMessage::replyMessageId), + make_column("replyPeerId", &DeletedMessage::replyPeerId), + make_column("replyTopId", &DeletedMessage::replyTopId), + make_column("replyForumTopic", &DeletedMessage::replyForumTopic), + make_column("entityCreateDate", &DeletedMessage::entityCreateDate), + make_column("text", &DeletedMessage::text), + make_column("textEntities", &DeletedMessage::textEntities), + make_column("mediaPath", &DeletedMessage::mediaPath), + make_column("hqThumbPath", &DeletedMessage::hqThumbPath), + make_column("documentType", &DeletedMessage::documentType), + make_column("documentSerialized", &DeletedMessage::documentSerialized), + make_column("thumbsSerialized", &DeletedMessage::thumbsSerialized), + make_column("documentAttributesSerialized", + &DeletedMessage::documentAttributesSerialized), + make_column("mimeType", &DeletedMessage::mimeType) + ), + make_table("EditedMessage", + make_column("userId", &EditedMessage::userId), + make_column("dialogId", &EditedMessage::dialogId), + make_column("groupedId", &EditedMessage::groupedId), + make_column("peerId", &EditedMessage::peerId), + make_column("fromId", &EditedMessage::fromId), + make_column("topicId", &EditedMessage::topicId), + make_column("messageId", &EditedMessage::messageId), + make_column("date", &EditedMessage::date), + make_column("flags", &EditedMessage::flags), + make_column("editDate", &EditedMessage::editDate), + make_column("views", &EditedMessage::views), + make_column("fwdFlags", &EditedMessage::fwdFlags), + make_column("fwdFromId", &EditedMessage::fwdFromId), + make_column("fwdName", &EditedMessage::fwdName), + make_column("fwdDate", &EditedMessage::fwdDate), + make_column("fwdPostAuthor", &EditedMessage::fwdPostAuthor), + make_column("replyFlags", &EditedMessage::replyFlags), + make_column("replyMessageId", &EditedMessage::replyMessageId), + make_column("replyPeerId", &EditedMessage::replyPeerId), + make_column("replyTopId", &EditedMessage::replyTopId), + make_column("replyForumTopic", &EditedMessage::replyForumTopic), + make_column("entityCreateDate", &EditedMessage::entityCreateDate), + make_column("text", &EditedMessage::text), + make_column("textEntities", &EditedMessage::textEntities), + make_column("mediaPath", &EditedMessage::mediaPath), + make_column("hqThumbPath", &EditedMessage::hqThumbPath), + make_column("documentType", &EditedMessage::documentType), + make_column("documentSerialized", &EditedMessage::documentSerialized), + make_column("thumbsSerialized", &EditedMessage::thumbsSerialized), + make_column("documentAttributesSerialized", + &EditedMessage::documentAttributesSerialized), + make_column("mimeType", &EditedMessage::mimeType) + ), + make_table("DeletedDialog", + make_column("userId", &DeletedDialog::userId), + make_column("dialogId", &DeletedDialog::dialogId), + make_column("peerId", &DeletedDialog::peerId), + make_column("topMessage", &DeletedDialog::topMessage), + make_column("lastMessageDate", &DeletedDialog::lastMessageDate), + make_column("flags", &DeletedDialog::flags), + make_column("entityCreateDate", &DeletedDialog::entityCreateDate) + ) ); namespace AyuDatabase { - void initialize() - { - storage.sync_schema(); - storage.begin_transaction(); - storage.commit(); - } +void initialize() +{ + storage.sync_schema(); + + storage.begin_transaction(); + storage.commit(); +} + +void addEditedMessage(const EditedMessage &message) +{ + storage.begin_transaction(); + storage.insert(message); + storage.commit(); +} + +std::vector getEditedMessages(ID userId, ID dialogId, ID messageId) +{ + return storage.get_all( + where( + c(&EditedMessage::userId) == userId and + c(&EditedMessage::dialogId) == dialogId and + c(&EditedMessage::messageId) == messageId + ) + ); +} + +bool hasRevisions(ID userId, ID dialogId, ID messageId) +{ + return storage.count( + where( + c(&EditedMessage::userId) == userId and + c(&EditedMessage::dialogId) == dialogId and + c(&EditedMessage::messageId) == messageId + ) + ) > 0; +} - void addEditedMessage(const EditedMessage& message) - { - storage.begin_transaction(); - storage.insert(message); - storage.commit(); - } - - std::vector getEditedMessages(ID userId, ID dialogId, ID messageId) - { - return storage.get_all( - where( - c(&EditedMessage::get_user_id) == userId and - c(&EditedMessage::get_dialog_id) == dialogId and - c(&EditedMessage::get_message_id) == messageId - ) - ); - } - - bool hasRevisions(ID userId, ID dialogId, ID messageId) - { - return storage.count( - where( - c(&EditedMessage::get_user_id) == userId and - c(&EditedMessage::get_dialog_id) == dialogId and - c(&EditedMessage::get_message_id) == messageId - ) - ) > 0; - } } diff --git a/Telegram/SourceFiles/ayu/database/ayu_database.h b/Telegram/SourceFiles/ayu/database/ayu_database.h index 7878beb45..8b9d42733 100644 --- a/Telegram/SourceFiles/ayu/database/ayu_database.h +++ b/Telegram/SourceFiles/ayu/database/ayu_database.h @@ -4,7 +4,6 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once #include "entities.h" @@ -13,9 +12,11 @@ namespace AyuDatabase { - void initialize(); - void addEditedMessage(const EditedMessage& message); - std::vector getEditedMessages(ID userId, ID dialogId, ID messageId); - bool hasRevisions(ID userId, ID dialogId, ID messageId); +void initialize(); + +void addEditedMessage(const EditedMessage &message); +std::vector getEditedMessages(ID userId, ID dialogId, ID messageId); +bool hasRevisions(ID userId, ID dialogId, ID messageId); + } diff --git a/Telegram/SourceFiles/ayu/database/entities.h b/Telegram/SourceFiles/ayu/database/entities.h index d0941bd08..5e2c9b0e4 100644 --- a/Telegram/SourceFiles/ayu/database/entities.h +++ b/Telegram/SourceFiles/ayu/database/entities.h @@ -4,18 +4,17 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once - #include #define ID long long -// https://github.com/AyuGram/AyuGram4A/blob/rewrite/TMessagesProj/src/main/java/com/radolyn/ayugram/database/entities/AyuMessageBase.java +template class AyuMessageBase { public: + ID fakeId; ID userId; ID dialogId; ID groupedId; @@ -24,449 +23,43 @@ public: ID topicId; int messageId; int date; - int flags; - int editDate; - bool editHide; - bool out; - + int views; + int fwdFlags; + ID fwdFromId; + std::string fwdName; + int fwdDate; + std::string fwdPostAuthor; + int replyFlags; + int replyMessageId; + ID replyPeerId; + int replyTopId; + bool replyForumTopic; int entityCreateDate; - - std::string text; // plain text - std::string textEntities; // TL serialized - std::string mediaPath; // full path - int documentType; // see DOCUMENT_TYPE_* - std::string documentSerialized; // for sticker; TL serialized - std::string thumbsSerialized; // for video/etc.; TL serialized - std::string documentAttributesSerialized; // for video/voice/etc.; TL serialized + std::string text; + std::vector textEntities; + std::string mediaPath; + std::string hqThumbPath; + int documentType; + std::vector documentSerialized; + std::vector thumbsSerialized; + std::vector documentAttributesSerialized; std::string mimeType; }; -class DeletedMessage : public AyuMessageBase +using DeletedMessage = AyuMessageBase; +using EditedMessage = AyuMessageBase; + +class DeletedDialog { public: - [[nodiscard]] long long get_user_id() const - { - return userId; - } - - void set_user_id(long long user_id) - { - userId = user_id; - } - - [[nodiscard]] long long get_dialog_id() const - { - return dialogId; - } - - void set_dialog_id(long long dialog_id) - { - dialogId = dialog_id; - } - - [[nodiscard]] long long get_grouped_id() const - { - return groupedId; - } - - void set_grouped_id(long long grouped_id) - { - groupedId = grouped_id; - } - - [[nodiscard]] long long get_peer_id() const - { - return peerId; - } - - void set_peer_id(long long peer_id) - { - peerId = peer_id; - } - - [[nodiscard]] long long get_from_id() const - { - return fromId; - } - - void set_from_id(long long from_id) - { - fromId = from_id; - } - - [[nodiscard]] long long get_topic_id() const - { - return topicId; - } - - void set_topic_id(long long topic_id) - { - topicId = topic_id; - } - - [[nodiscard]] int get_message_id() const - { - return messageId; - } - - void set_message_id(int message_id) - { - messageId = message_id; - } - - [[nodiscard]] int get_date() const - { - return date; - } - - void set_date(int date) - { - this->date = date; - } - - [[nodiscard]] int get_flags() const - { - return flags; - } - - void set_flags(int flags) - { - this->flags = flags; - } - - [[nodiscard]] int get_edit_date() const - { - return editDate; - } - - void set_edit_date(int edit_date) - { - editDate = edit_date; - } - - [[nodiscard]] bool is_edit_hide() const - { - return editHide; - } - - void set_edit_hide(bool edit_hide) - { - editHide = edit_hide; - } - - [[nodiscard]] bool is_out() const - { - return out; - } - - void set_out(bool out) - { - this->out = out; - } - - [[nodiscard]] int get_entity_create_date() const - { - return entityCreateDate; - } - - void set_entity_create_date(int entity_create_date) - { - entityCreateDate = entity_create_date; - } - - [[nodiscard]] std::string get_text() const - { - return text; - } - - void set_text(const std::string& text) - { - this->text = text; - } - - [[nodiscard]] std::string get_text_entities() const - { - return textEntities; - } - - void set_text_entities(const std::string& text_entities) - { - textEntities = text_entities; - } - - [[nodiscard]] std::string get_media_path() const - { - return mediaPath; - } - - void set_media_path(const std::string& media_path) - { - mediaPath = media_path; - } - - [[nodiscard]] int get_document_type() const - { - return documentType; - } - - void set_document_type(int document_type) - { - documentType = document_type; - } - - [[nodiscard]] std::string get_document_serialized() const - { - return documentSerialized; - } - - void set_document_serialized(const std::string& document_serialized) - { - documentSerialized = document_serialized; - } - - [[nodiscard]] std::string get_thumbs_serialized() const - { - return thumbsSerialized; - } - - void set_thumbs_serialized(const std::string& thumbs_serialized) - { - thumbsSerialized = thumbs_serialized; - } - - [[nodiscard]] std::string get_document_attributes_serialized() const - { - return documentAttributesSerialized; - } - - void set_document_attributes_serialized(const std::string& document_attributes_serialized) - { - documentAttributesSerialized = document_attributes_serialized; - } - - [[nodiscard]] std::string get_mime_type() const - { - return mimeType; - } - - void set_mime_type(const std::string& mime_type) - { - mimeType = mime_type; - } -}; - -class EditedMessage : public AyuMessageBase -{ -public: - [[nodiscard]] long long get_user_id() const - { - return userId; - } - - void set_user_id(long long user_id) - { - userId = user_id; - } - - [[nodiscard]] long long get_dialog_id() const - { - return dialogId; - } - - void set_dialog_id(long long dialog_id) - { - dialogId = dialog_id; - } - - [[nodiscard]] long long get_grouped_id() const - { - return groupedId; - } - - void set_grouped_id(long long grouped_id) - { - groupedId = grouped_id; - } - - [[nodiscard]] long long get_peer_id() const - { - return peerId; - } - - void set_peer_id(long long peer_id) - { - peerId = peer_id; - } - - [[nodiscard]] long long get_from_id() const - { - return fromId; - } - - void set_from_id(long long from_id) - { - fromId = from_id; - } - - [[nodiscard]] long long get_topic_id() const - { - return topicId; - } - - void set_topic_id(long long topic_id) - { - topicId = topic_id; - } - - [[nodiscard]] int get_message_id() const - { - return messageId; - } - - void set_message_id(int message_id) - { - messageId = message_id; - } - - [[nodiscard]] int get_date() const - { - return date; - } - - void set_date(int date) - { - this->date = date; - } - - [[nodiscard]] int get_flags() const - { - return flags; - } - - void set_flags(int flags) - { - this->flags = flags; - } - - [[nodiscard]] int get_edit_date() const - { - return editDate; - } - - void set_edit_date(int edit_date) - { - editDate = edit_date; - } - - [[nodiscard]] bool is_edit_hide() const - { - return editHide; - } - - void set_edit_hide(bool edit_hide) - { - editHide = edit_hide; - } - - [[nodiscard]] bool is_out() const - { - return out; - } - - void set_out(bool out) - { - this->out = out; - } - - [[nodiscard]] int get_entity_create_date() const - { - return entityCreateDate; - } - - void set_entity_create_date(int entity_create_date) - { - entityCreateDate = entity_create_date; - } - - [[nodiscard]] std::string get_text() const - { - return text; - } - - void set_text(const std::string& text) - { - this->text = text; - } - - [[nodiscard]] std::string get_text_entities() const - { - return textEntities; - } - - void set_text_entities(const std::string& text_entities) - { - textEntities = text_entities; - } - - [[nodiscard]] std::string get_media_path() const - { - return mediaPath; - } - - void set_media_path(const std::string& media_path) - { - mediaPath = media_path; - } - - [[nodiscard]] int get_document_type() const - { - return documentType; - } - - void set_document_type(int document_type) - { - documentType = document_type; - } - - [[nodiscard]] std::string get_document_serialized() const - { - return documentSerialized; - } - - void set_document_serialized(const std::string& document_serialized) - { - documentSerialized = document_serialized; - } - - [[nodiscard]] std::string get_thumbs_serialized() const - { - return thumbsSerialized; - } - - void set_thumbs_serialized(const std::string& thumbs_serialized) - { - thumbsSerialized = thumbs_serialized; - } - - [[nodiscard]] std::string get_document_attributes_serialized() const - { - return documentAttributesSerialized; - } - - void set_document_attributes_serialized(const std::string& document_attributes_serialized) - { - documentAttributesSerialized = document_attributes_serialized; - } - - [[nodiscard]] std::string get_mime_type() const - { - return mimeType; - } - - void set_mime_type(const std::string& mime_type) - { - mimeType = mime_type; - } + ID fakeId; + ID userId; + ID dialogId; + ID peerId; + int topMessage; + int lastMessageDate; + int flags; + int entityCreateDate; }; diff --git a/Telegram/SourceFiles/ayu/features/streamer_mode/streamer_mode.h b/Telegram/SourceFiles/ayu/features/streamer_mode/streamer_mode.h index d5b747dfd..bcf0b9482 100644 --- a/Telegram/SourceFiles/ayu/features/streamer_mode/streamer_mode.h +++ b/Telegram/SourceFiles/ayu/features/streamer_mode/streamer_mode.h @@ -4,16 +4,17 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once #include "ui/rp_widget.h" namespace AyuFeatures::StreamerMode { - bool isEnabled(); - void enable(); - void disable(); - void hideWidgetWindow(QWidget* widget); - void showWidgetWindow(QWidget* widget); + +bool isEnabled(); +void enable(); +void disable(); +void hideWidgetWindow(QWidget *widget); +void showWidgetWindow(QWidget *widget); + } diff --git a/Telegram/SourceFiles/ayu/features/streamer_mode/streamer_mode_linux.cpp b/Telegram/SourceFiles/ayu/features/streamer_mode/streamer_mode_linux.cpp index 98bf0c696..aa782820c 100644 --- a/Telegram/SourceFiles/ayu/features/streamer_mode/streamer_mode_linux.cpp +++ b/Telegram/SourceFiles/ayu/features/streamer_mode/streamer_mode_linux.cpp @@ -14,30 +14,34 @@ namespace AyuFeatures::StreamerMode { - bool isEnabledVal; - bool isEnabled() - { - return isEnabledVal; - } +bool isEnabledVal; - void enable() - { - isEnabledVal = true; - } +bool isEnabled() +{ + return isEnabledVal; +} - void disable() - { - isEnabledVal = false; - } +void enable() +{ + isEnabledVal = true; +} - void hideWidgetWindow(QWidget* widget) { +void disable() +{ + isEnabledVal = false; +} - } +void hideWidgetWindow(QWidget *widget) +{ - void showWidgetWindow(QWidget* widget) { +} + +void showWidgetWindow(QWidget *widget) +{ + +} - } } #endif diff --git a/Telegram/SourceFiles/ayu/features/streamer_mode/streamer_mode_windows.cpp b/Telegram/SourceFiles/ayu/features/streamer_mode/streamer_mode_windows.cpp index a61f3f8ac..9ee7fd6f1 100644 --- a/Telegram/SourceFiles/ayu/features/streamer_mode/streamer_mode_windows.cpp +++ b/Telegram/SourceFiles/ayu/features/streamer_mode/streamer_mode_windows.cpp @@ -14,40 +14,42 @@ namespace AyuFeatures::StreamerMode { - bool isEnabledVal; - bool isEnabled() - { - return isEnabledVal; - } +bool isEnabledVal; - void enable() - { - auto handle = Core::App().activeWindow()->widget()->psHwnd(); - SetWindowDisplayAffinity(handle, WDA_EXCLUDEFROMCAPTURE); +bool isEnabled() +{ + return isEnabledVal; +} - isEnabledVal = true; - } +void enable() +{ + auto handle = Core::App().activeWindow()->widget()->psHwnd(); + SetWindowDisplayAffinity(handle, WDA_EXCLUDEFROMCAPTURE); - void disable() - { - auto handle = Core::App().activeWindow()->widget()->psHwnd(); - SetWindowDisplayAffinity(handle, WDA_NONE); + isEnabledVal = true; +} - isEnabledVal = false; - } +void disable() +{ + auto handle = Core::App().activeWindow()->widget()->psHwnd(); + SetWindowDisplayAffinity(handle, WDA_NONE); - void hideWidgetWindow(QWidget* widget) - { - auto handle = reinterpret_cast(widget->window()->winId()); - SetWindowDisplayAffinity(handle, WDA_EXCLUDEFROMCAPTURE); - } + isEnabledVal = false; +} + +void hideWidgetWindow(QWidget *widget) +{ + auto handle = reinterpret_cast(widget->window()->winId()); + SetWindowDisplayAffinity(handle, WDA_EXCLUDEFROMCAPTURE); +} + +void showWidgetWindow(QWidget *widget) +{ + auto handle = reinterpret_cast(widget->window()->winId()); + SetWindowDisplayAffinity(handle, WDA_NONE); +} - void showWidgetWindow(QWidget* widget) - { - auto handle = reinterpret_cast(widget->window()->winId()); - SetWindowDisplayAffinity(handle, WDA_NONE); - } } #endif diff --git a/Telegram/SourceFiles/ayu/messages/ayu_messages_controller.cpp b/Telegram/SourceFiles/ayu/messages/ayu_messages_controller.cpp index d7c890404..0dd4cefcd 100644 --- a/Telegram/SourceFiles/ayu/messages/ayu_messages_controller.cpp +++ b/Telegram/SourceFiles/ayu/messages/ayu_messages_controller.cpp @@ -9,6 +9,7 @@ #include "ayu/ayu_constants.h" #include "ayu/database/ayu_database.h" +#include "ayu/utils/ayu_mapper.h" #include "ayu/utils/telegram_helpers.h" #include "base/unixtime.h" @@ -18,87 +19,91 @@ #include "history/history.h" #include "history/history_item.h" +#include "history/history_item_components.h" #include "main/main_session.h" namespace AyuMessages { - std::optional controller = std::nullopt; - void initialize() - { - if (controller.has_value()) - { - return; - } +std::optional controller = std::nullopt; - controller = ayu_messages_controller(); +void initialize() +{ + if (controller.has_value()) { + return; } - ayu_messages_controller& getInstance() - { - initialize(); - return controller.value(); - } - - void map(HistoryMessageEdition& edition, not_null item, AyuMessageBase& message) - { - message.userId = item->history()->owner().session().userId().bare; - message.dialogId = getDialogIdFromPeer(item->history()->peer); - message.groupedId = item->groupId().value; - message.peerId = item->from()->id.value; // todo: ??? - message.fromId = item->from()->id.value; - if (auto topic = item->history()->asTopic()) - { - message.topicId = topic->rootId().bare; - } - message.messageId = item->id.bare; - message.date = item->date(); - - // message.flags = todo: - - message.editDate = edition.editDate; - message.editHide = item->hideEditedBadge(); - message.out = item->out(); - message.entityCreateDate = base::unixtime::now(); // todo: rework - - auto serializedText = serializeTextWithEntities(item); - message.text = serializedText.first; - message.textEntities = serializedText.second; - - // todo: - message.mediaPath = "/"; - message.documentType = DOCUMENT_TYPE_NONE; - - // message.documentSerialized; - // message.thumbsSerialized; - // message.documentAttributesSerialized; - // message.mimeType; - } - - void ayu_messages_controller::addEditedMessage(HistoryMessageEdition& edition, not_null item) - { - EditedMessage message; - map(edition, item, message); - - AyuDatabase::addEditedMessage(message); - } - - std::vector ayu_messages_controller::getEditedMessages(HistoryItem* item) - { - auto userId = item->history()->owner().session().userId().bare; - auto dialogId = getDialogIdFromPeer(item->history()->peer); - auto msgId = item->id.bare; - - return AyuDatabase::getEditedMessages(userId, dialogId, msgId); - } - - bool ayu_messages_controller::hasRevisions(not_null item) - { - auto userId = item->history()->owner().session().userId().bare; - auto dialogId = getDialogIdFromPeer(item->history()->peer); - auto msgId = item->id.bare; - - return AyuDatabase::hasRevisions(userId, dialogId, msgId); - } + controller = ayu_messages_controller(); +} + +ayu_messages_controller &getInstance() +{ + initialize(); + return controller.value(); +} + +void map(HistoryMessageEdition &edition, not_null item, EditedMessage &message) +{ + message.userId = item->history()->owner().session().userId().bare; + message.dialogId = getDialogIdFromPeer(item->history()->peer); + message.groupedId = item->groupId().value; + message.peerId = item->from()->id.value; // todo: ??? + message.fromId = item->from()->id.value; + if (auto topic = item->history()->asTopic()) { + message.topicId = topic->rootId().bare; + } + message.messageId = item->id.bare; + message.date = item->date(); + message.flags = AyuMapper::mapItemFlagsToMTPFlags(item); + + if (auto edited = item->Get()) { + message.editDate = edited->date; + } else { + message.editDate = base::unixtime::now(); + } + + message.views = item->viewsCount(); + message.entityCreateDate = base::unixtime::now(); // todo: rework + + auto serializedText = serializeTextWithEntities(item); + message.text = serializedText.first; +// message.textEntities = serializedText.second; + + // todo: + message.mediaPath = "/"; + message.documentType = DOCUMENT_TYPE_NONE; + + // message.documentSerialized; + // message.thumbsSerialized; + // message.documentAttributesSerialized; + // message.mimeType; +} + +void ayu_messages_controller::addEditedMessage(HistoryMessageEdition &edition, not_null item) +{ + EditedMessage message; + map(edition, item, message); + + AyuDatabase::addEditedMessage(message); +} + +std::vector ayu_messages_controller::getEditedMessages(HistoryItem *item) +{ + auto userId = item->history()->owner().session().userId().bare; + auto dialogId = getDialogIdFromPeer(item->history()->peer); + auto msgId = item->id.bare; + + return AyuDatabase::getEditedMessages(userId, dialogId, msgId); +} + +bool ayu_messages_controller::hasRevisions(not_null item) +{ + auto userId = item->history()->owner().session().userId().bare; + auto dialogId = getDialogIdFromPeer(item->history()->peer); + auto msgId = item->id.bare; + + return AyuDatabase::hasRevisions(userId, dialogId, msgId); +} + } diff --git a/Telegram/SourceFiles/ayu/messages/ayu_messages_controller.h b/Telegram/SourceFiles/ayu/messages/ayu_messages_controller.h index 94ec4b5a1..3afc77162 100644 --- a/Telegram/SourceFiles/ayu/messages/ayu_messages_controller.h +++ b/Telegram/SourceFiles/ayu/messages/ayu_messages_controller.h @@ -4,7 +4,6 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once #include "ayu/database/entities.h" @@ -13,13 +12,15 @@ namespace AyuMessages { - class ayu_messages_controller - { - public: - void addEditedMessage(HistoryMessageEdition& edition, not_null item); - std::vector getEditedMessages(HistoryItem* item); - bool hasRevisions(not_null item); - }; - ayu_messages_controller& getInstance(); +class ayu_messages_controller +{ +public: + void addEditedMessage(HistoryMessageEdition &edition, not_null item); + std::vector getEditedMessages(HistoryItem *item); + bool hasRevisions(not_null item); +}; + +ayu_messages_controller &getInstance(); + } diff --git a/Telegram/SourceFiles/ayu/sync/ayu_sync_controller.cpp b/Telegram/SourceFiles/ayu/sync/ayu_sync_controller.cpp index f61298d09..1ce81db64 100644 --- a/Telegram/SourceFiles/ayu/sync/ayu_sync_controller.cpp +++ b/Telegram/SourceFiles/ayu/sync/ayu_sync_controller.cpp @@ -24,198 +24,183 @@ namespace AyuSync { - std::optional controller = std::nullopt; - bool isAgentDownloaded() - { - return std::filesystem::exists(AgentPath); +std::optional controller = std::nullopt; + +bool isAgentDownloaded() +{ + return std::filesystem::exists(AgentPath); +} + +bool isAgentRunning() +{ + return isProcessRunning(AgentFilename); +} + +void initialize() +{ + if (controller.has_value()) { + return; } - bool isAgentRunning() - { - return isProcessRunning(AgentFilename); + controller = ayu_sync_controller(); +} + +ayu_sync_controller &getInstance() +{ + initialize(); + return controller.value(); +} + +void ayu_sync_controller::initializeAgent() +{ + if (!isAgentDownloaded()) { + return; } - void initialize() - { - if (controller.has_value()) - { - return; + if (isAgentRunning()) { + killProcess(AgentFilename); + } + + auto configPath = std::filesystem::absolute("./tdata/sync_preferences.json"); + auto process = nes::process{AgentPath, {configPath.string(), ""}, nes::process_options::none}; + process.detach(); + + std::thread receiverThread(&ayu_sync_controller::receiver, this); + receiverThread.detach(); + + initialized = true; +} + +void ayu_sync_controller::syncRead(not_null history, MsgId untilId) +{ + if (!initialized) { + return; + } + + SyncRead ev; + ev.userId = history->owner().session().userId().bare; + + ev.args.dialogId = getDialogIdFromPeer(history->peer); + ev.args.untilId = untilId.bare; + ev.args.unread = history->unreadCount(); + + pipe->send(ev); +} + +void ayu_sync_controller::receiver() +{ + pipe = std::make_unique(); + pipe->connect(); + + LOG(("Pipe created")); + + while (true) { + auto p = pipe->receive(); + if (p == std::nullopt) { + continue; } - controller = ayu_sync_controller(); + std::string s = p->dump(); + LOG(("[AyuSync] Received message: %1").arg(QString::fromStdString(s))); + + invokeHandler(p.value()); + } +} + +void ayu_sync_controller::invokeHandler(json p) +{ + LOG(("Invoking handler on %1").arg(p.dump().c_str())); + + auto userId = p["userId"].get(); + auto type = p["type"].get(); + + LOG(("userId: %1, type: %2").arg(userId).arg(type.c_str())); + + if (!accountExists(userId)) { + LOG(("Sync for unknown account: %1").arg(userId)); + return; } - ayu_sync_controller& getInstance() - { - initialize(); - return controller.value(); + if (type == "sync_force") { + auto ev = p.get(); + onSyncForce(ev); } - - void ayu_sync_controller::initializeAgent() - { - if (!isAgentDownloaded()) - { - return; - } - - if (isAgentRunning()) - { - killProcess(AgentFilename); - } - - auto configPath = std::filesystem::absolute("./tdata/sync_preferences.json"); - auto process = nes::process{AgentPath, {configPath.string(), ""}, nes::process_options::none}; - process.detach(); - - std::thread receiverThread(&ayu_sync_controller::receiver, this); - receiverThread.detach(); - - initialized = true; + else if (type == "sync_batch") { + onSyncBatch(p); } - - void ayu_sync_controller::syncRead(not_null history, MsgId untilId) - { - if (!initialized) - { - return; - } - - SyncRead ev; - ev.userId = history->owner().session().userId().bare; - - ev.args.dialogId = getDialogIdFromPeer(history->peer); - ev.args.untilId = untilId.bare; - ev.args.unread = history->unreadCount(); - - pipe->send(ev); + else if (type == "sync_read") { + auto ev = p.get(); + onSyncRead(ev); } + else { + LOG(("Unknown sync type: %1").arg(type.c_str())); + } +} - void ayu_sync_controller::receiver() - { - pipe = std::make_unique(); - pipe->connect(); +void ayu_sync_controller::onSyncForce(SyncForce ev) +{ + auto session = getSession(ev.userId); + auto histories = session->data().chatsList(); - LOG(("Pipe created")); + SyncBatch readsBatchEvent; + readsBatchEvent.userId = ev.userId; - while (true) - { - auto p = pipe->receive(); - if (p == std::nullopt) - { + for (const auto &row : histories->indexed()->all()) { + if (const auto history = row->history()) { + auto dialogId = getDialogIdFromPeer(history->peer); + + SyncRead readEv; + readEv.userId = ev.userId; + + history->calculateFirstUnreadMessage(); + auto unreadElement = history->firstUnreadMessage(); + + if (!unreadElement && history->unreadCount()) { + LOG(("No unread can be calculated for %1").arg(dialogId)); continue; } - std::string s = p->dump(); - LOG(("[AyuSync] Received message: %1").arg(QString::fromStdString(s))); + auto untilId = unreadElement ? unreadElement->data()->id.bare : history->lastMessage()->id.bare; - invokeHandler(p.value()); + readEv.args.dialogId = dialogId; + readEv.args.untilId = untilId; + readEv.args.unread = history->unreadCount(); + + readsBatchEvent.args.events.emplace_back(readEv); } } - void ayu_sync_controller::invokeHandler(json p) - { - LOG(("Invoking handler on %1").arg(p.dump().c_str())); + pipe->send(readsBatchEvent); - auto userId = p["userId"].get(); - auto type = p["type"].get(); + // send finish event + SyncForceFinish newEv; + newEv.userId = ev.userId; - LOG(("userId: %1, type: %2").arg(userId).arg(type.c_str())); + pipe->send(newEv); +} - if (!accountExists(userId)) - { - LOG(("Sync for unknown account: %1").arg(userId)); - return; - } - - if (type == "sync_force") - { - auto ev = p.get(); - onSyncForce(ev); - } - else if (type == "sync_batch") - { - onSyncBatch(p); - } - else if (type == "sync_read") - { - auto ev = p.get(); - onSyncRead(ev); - } - else - { - LOG(("Unknown sync type: %1").arg(type.c_str())); - } - } - - void ayu_sync_controller::onSyncForce(SyncForce ev) - { - auto session = getSession(ev.userId); - auto histories = session->data().chatsList(); - - SyncBatch readsBatchEvent; - readsBatchEvent.userId = ev.userId; - - for (const auto& row : histories->indexed()->all()) - { - if (const auto history = row->history()) - { - auto dialogId = getDialogIdFromPeer(history->peer); - - SyncRead readEv; - readEv.userId = ev.userId; - - history->calculateFirstUnreadMessage(); - auto unreadElement = history->firstUnreadMessage(); - - if (!unreadElement && history->unreadCount()) - { - LOG(("No unread can be calculated for %1").arg(dialogId)); - continue; - } - - auto untilId = unreadElement ? unreadElement->data()->id.bare : history->lastMessage()->id.bare; - - readEv.args.dialogId = dialogId; - readEv.args.untilId = untilId; - readEv.args.unread = history->unreadCount(); - - readsBatchEvent.args.events.emplace_back(readEv); - } - } - - pipe->send(readsBatchEvent); - - // send finish event - SyncForceFinish newEv; - newEv.userId = ev.userId; - - pipe->send(newEv); - } - - void ayu_sync_controller::onSyncBatch(json ev) - { - for (auto& item : ev["args"]["events"]) - { - invokeHandler(item); - } - } - - void ayu_sync_controller::onSyncRead(SyncRead ev) - { - dispatchToMainThread([=] - { - auto session = getSession(ev.userId); - auto history = getHistoryFromDialogId(ev.args.dialogId, session); - - if (history->folderKnown()) - { - history->inboxRead(ev.args.untilId, ev.args.unread); - } - else - { - LOG(("Unknown dialog %1").arg(ev.args.dialogId)); - } - }); +void ayu_sync_controller::onSyncBatch(json ev) +{ + for (auto &item : ev["args"]["events"]) { + invokeHandler(item); } } + +void ayu_sync_controller::onSyncRead(SyncRead ev) +{ + dispatchToMainThread([=] + { + auto session = getSession(ev.userId); + auto history = getHistoryFromDialogId(ev.args.dialogId, session); + + if (history->folderKnown()) { + history->inboxRead(ev.args.untilId, ev.args.unread); + } + else { + LOG(("Unknown dialog %1").arg(ev.args.dialogId)); + } + }); +} + +} diff --git a/Telegram/SourceFiles/ayu/sync/ayu_sync_controller.h b/Telegram/SourceFiles/ayu/sync/ayu_sync_controller.h index 47eab89b3..63738ea39 100644 --- a/Telegram/SourceFiles/ayu/sync/ayu_sync_controller.h +++ b/Telegram/SourceFiles/ayu/sync/ayu_sync_controller.h @@ -4,7 +4,6 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once #include "models.h" @@ -16,39 +15,44 @@ using json = nlohmann::json; -const std::string AgentFilename = #ifdef _WIN32 - "AyuSync.Agent.exe"; + +const std::string AgentFilename = "AyuSync.Agent.exe"; + #else - "AyuSync.Agent"; + +const std::string AgentFilename = "AyuSync.Agent"; + #endif const std::string AgentPath = "./AyuSync/" + AgentFilename; namespace AyuSync { - class ayu_sync_controller - { - public: - void initializeAgent(); - void syncRead(not_null history, MsgId untilId); +class ayu_sync_controller +{ +public: + void initializeAgent(); - void onSyncForce(SyncForce ev); - void onSyncBatch(json ev); - void onSyncRead(SyncRead ev); + void syncRead(not_null history, MsgId untilId); - void invokeHandler(json p); + void onSyncForce(SyncForce ev); + void onSyncBatch(json ev); + void onSyncRead(SyncRead ev); - private: - void receiver(); + void invokeHandler(json p); - std::unique_ptr pipe; - bool initialized; - }; +private: + void receiver(); - ayu_sync_controller& getInstance(); + std::unique_ptr pipe; + bool initialized; +}; + +ayu_sync_controller &getInstance(); + +bool isAgentDownloaded(); +bool isAgentRunning(); - bool isAgentDownloaded(); - bool isAgentRunning(); } diff --git a/Telegram/SourceFiles/ayu/sync/models.h b/Telegram/SourceFiles/ayu/sync/models.h index 024262a6f..15e422265 100644 --- a/Telegram/SourceFiles/ayu/sync/models.h +++ b/Telegram/SourceFiles/ayu/sync/models.h @@ -1,10 +1,15 @@ +// 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, 2023 #pragma once -#include "ayu/libs/json.hpp" - #include #include +#include "ayu/libs/json.hpp" #include "ayu/database/entities.h" using json = nlohmann::json; @@ -30,7 +35,41 @@ public: std::vector events; }; - SyncBatchArgs args; + SyncBatchArgs args{}; +}; + +class SyncForce : public SyncEvent +{ +public: + explicit SyncForce() + { + type = "sync_force"; + } + + class SyncForceArgs + { + public: + int fromDate; + }; + + SyncForceArgs args{}; +}; + +class SyncForceFinish : public SyncEvent +{ +public: + explicit SyncForceFinish() + { + type = "sync_force_finish"; + } + + class SyncForceFinishArgs + { + public: + short dummy; // required to be JSON serializable + }; + + SyncForceFinishArgs args{}; }; class SyncRead : public SyncEvent @@ -49,50 +88,53 @@ public: int unread; }; - SyncReadArgs args; + SyncReadArgs args{}; }; -class SyncForce : public SyncEvent +class SyncDeletedMessage : public SyncEvent { public: - explicit SyncForce() + explicit SyncDeletedMessage() { - type = "sync_force"; + type = "sync_deleted_message"; } - class SyncForceArgs + class SyncDeletedMessageArgs { public: - int fromDate; + json message; }; - SyncForceArgs args; + SyncDeletedMessageArgs args{}; }; -class SyncForceFinish : public SyncEvent +class SyncEditedMessage : public SyncEvent { public: - explicit SyncForceFinish() + explicit SyncEditedMessage() { - type = "sync_force_finish"; + type = "sync_edited_message"; } - class SyncForceFinishArgs + class SyncEditedMessageArgs { public: - short dummy; // required to be JSON serializable + json message; }; - SyncForceFinishArgs args; + SyncEditedMessageArgs args{}; }; - NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SyncEvent, type, userId) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SyncBatch::SyncBatchArgs, events) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SyncBatch, type, userId, args) -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SyncRead::SyncReadArgs, dialogId, untilId, unread) -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SyncRead, type, userId, args) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SyncForce::SyncForceArgs, fromDate) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SyncForce, type, userId, args) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SyncForceFinish::SyncForceFinishArgs, dummy) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SyncForceFinish, type, userId, args) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SyncRead::SyncReadArgs, dialogId, untilId, unread) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SyncRead, type, userId, args) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SyncDeletedMessage::SyncDeletedMessageArgs, message) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SyncDeletedMessage, type, userId, args) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SyncEditedMessage::SyncEditedMessageArgs, message) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SyncEditedMessage, type, userId, args) diff --git a/Telegram/SourceFiles/ayu/sync/utils/ayu_pipe_wrapper.cpp b/Telegram/SourceFiles/ayu/sync/utils/ayu_pipe_wrapper.cpp index 732f9a24e..ff8a0c9f1 100644 --- a/Telegram/SourceFiles/ayu/sync/utils/ayu_pipe_wrapper.cpp +++ b/Telegram/SourceFiles/ayu/sync/utils/ayu_pipe_wrapper.cpp @@ -26,14 +26,13 @@ void ayu_pipe_wrapper::send(json p) bit_converter::i32_to_bytes(length, false, lengthBuff); os->write(lengthBuff, 4); - os->write(reinterpret_cast(s.c_str()), length); + os->write(reinterpret_cast(s.c_str()), length); os->flush(); } std::optional ayu_pipe_wrapper::receive() { - if (!is->is_open()) - { + if (!is->is_open()) { return std::nullopt; } @@ -44,16 +43,14 @@ std::optional ayu_pipe_wrapper::receive() LOG(("ayu_pipe_wrapper::receive() length: %1").arg(length)); - if (length <= 0) - { + if (length <= 0) { return std::nullopt; } auto sb = stringbuf(); unsigned char buff[4096]; - while (length > 0) - { + while (length > 0) { auto readSize = std::min(length, static_cast(sizeof(buff))); is->read(buff, readSize); diff --git a/Telegram/SourceFiles/ayu/sync/utils/ayu_pipe_wrapper.h b/Telegram/SourceFiles/ayu/sync/utils/ayu_pipe_wrapper.h index 0f9b31ed0..26264755f 100644 --- a/Telegram/SourceFiles/ayu/sync/utils/ayu_pipe_wrapper.h +++ b/Telegram/SourceFiles/ayu/sync/utils/ayu_pipe_wrapper.h @@ -4,7 +4,6 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once #include "ayu/libs/json.hpp" diff --git a/Telegram/SourceFiles/ayu/sync/utils/process_utils.hpp b/Telegram/SourceFiles/ayu/sync/utils/process_utils.hpp index 8fe6aa146..d126aded2 100644 --- a/Telegram/SourceFiles/ayu/sync/utils/process_utils.hpp +++ b/Telegram/SourceFiles/ayu/sync/utils/process_utils.hpp @@ -1,3 +1,10 @@ +// 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, 2023 + #include #include #include @@ -12,31 +19,27 @@ // A function to check if a process is running by its name // Bing AI generated -inline bool isProcessRunning(const std::string& name) +inline bool isProcessRunning(const std::string &name) { #ifdef _WIN32 // Create a snapshot of all processes HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (snapshot == INVALID_HANDLE_VALUE) - { + if (snapshot == INVALID_HANDLE_VALUE) { std::cerr << "Failed to create snapshot\n"; return false; } // Iterate over the processes and compare the names PROCESSENTRY32 entry; entry.dwSize = sizeof(entry); - if (!Process32First(snapshot, &entry)) - { + if (!Process32First(snapshot, &entry)) { std::cerr << "Failed to get first process\n"; CloseHandle(snapshot); return false; } - do - { + do { std::wstring_convert> converter; std::string entry_name = converter.to_bytes(entry.szExeFile); - if (name == entry_name) - { + if (name == entry_name) { // Found a match CloseHandle(snapshot); return true; @@ -52,34 +55,29 @@ inline bool isProcessRunning(const std::string& name) } // Copilot generated -void killProcess(const std::string& name) +void killProcess(const std::string &name) { #ifdef _WIN32 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (snapshot == INVALID_HANDLE_VALUE) - { + if (snapshot == INVALID_HANDLE_VALUE) { std::cerr << "Failed to create snapshot\n"; return; } // Iterate over the processes and compare the names PROCESSENTRY32 entry; entry.dwSize = sizeof(entry); - if (!Process32First(snapshot, &entry)) - { + if (!Process32First(snapshot, &entry)) { std::cerr << "Failed to get first process\n"; CloseHandle(snapshot); return; } - do - { + do { std::wstring_convert> converter; std::string entry_name = converter.to_bytes(entry.szExeFile); - if (name == entry_name) - { + if (name == entry_name) { // Found a match HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, entry.th32ProcessID); - if (hProcess != nullptr) - { + if (hProcess != nullptr) { TerminateProcess(hProcess, 9); CloseHandle(hProcess); } diff --git a/Telegram/SourceFiles/ayu/ui/ayu_assets.cpp b/Telegram/SourceFiles/ayu/ui/ayu_assets.cpp index b0e0d89dc..faf127127 100644 --- a/Telegram/SourceFiles/ayu/ui/ayu_assets.cpp +++ b/Telegram/SourceFiles/ayu/ui/ayu_assets.cpp @@ -9,10 +9,13 @@ #include "ayu/ayu_settings.h" static QString LAST_LOADED_NAME; + static QImage LAST_LOADED; + static QImage LAST_LOADED_NO_MARGIN; -void loadAppIco() { +void loadAppIco() +{ auto settings = &AyuSettings::getInstance(); QString appDataPath = QDir::fromNativeSeparators(qgetenv("APPDATA")); @@ -31,8 +34,7 @@ void loadAppIco() { void loadIcons() { auto settings = &AyuSettings::getInstance(); - if (LAST_LOADED_NAME != settings->appIcon) - { + if (LAST_LOADED_NAME != settings->appIcon) { LAST_LOADED_NAME = settings->appIcon; LAST_LOADED = QImage(qsl(":/gui/art/ayu/%1/logo256.png").arg(settings->appIcon)); diff --git a/Telegram/SourceFiles/ayu/ui/ayu_assets.h b/Telegram/SourceFiles/ayu/ui/ayu_assets.h index f4840a05b..9469d0111 100644 --- a/Telegram/SourceFiles/ayu/ui/ayu_assets.h +++ b/Telegram/SourceFiles/ayu/ui/ayu_assets.h @@ -4,7 +4,6 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once void loadAppIco(); diff --git a/Telegram/SourceFiles/ayu/ui/ayu_lottie.cpp b/Telegram/SourceFiles/ayu/ui/ayu_lottie.cpp index 6a5a8f00b..7b68dd3ff 100644 --- a/Telegram/SourceFiles/ayu/ui/ayu_lottie.cpp +++ b/Telegram/SourceFiles/ayu/ui/ayu_lottie.cpp @@ -9,10 +9,12 @@ namespace AyuUi { - std::unique_ptr getLottie(const QString& text) - { - // todo: some kind of mapping - // Lottie::MakeIcon({.json = QString(), .sizeOverride = {24, 24}}); - return nullptr; - } + +std::unique_ptr getLottie(const QString &text) +{ + // todo: some kind of mapping + // Lottie::MakeIcon({.json = QString(), .sizeOverride = {24, 24}}); + return nullptr; +} + } diff --git a/Telegram/SourceFiles/ayu/ui/ayu_lottie.h b/Telegram/SourceFiles/ayu/ui/ayu_lottie.h index 41c38b11e..19350f668 100644 --- a/Telegram/SourceFiles/ayu/ui/ayu_lottie.h +++ b/Telegram/SourceFiles/ayu/ui/ayu_lottie.h @@ -4,12 +4,13 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once #include "lottie/lottie_icon.h" namespace AyuUi { - std::unique_ptr getLottie(const QString& text); + +std::unique_ptr getLottie(const QString &text); + } diff --git a/Telegram/SourceFiles/ayu/ui/boxes/confirmation_box.cpp b/Telegram/SourceFiles/ayu/ui/boxes/confirmation_box.cpp index 6343e7d50..e8208b8a3 100644 --- a/Telegram/SourceFiles/ayu/ui/boxes/confirmation_box.cpp +++ b/Telegram/SourceFiles/ayu/ui/boxes/confirmation_box.cpp @@ -17,48 +17,52 @@ namespace AyuUi { - ConfirmationBox::ConfirmationBox( - QWidget*, - not_null controller) : _controller(controller) - { - // - } - void ConfirmationBox::prepare() - { - _text.create(this, tr::ayu_ReadConfirmationBoxQuestion(), st::boxLabel); - - auto fullHeight = st::boxPadding.top() - + _text->height() - + st::boxPadding.bottom(); - - setDimensions(st::boxWidth, fullHeight); - - addButton(tr::ayu_ReadConfirmationBoxActionText(), [=, this] - { - ReadAllPeers(); - closeBox(); - }); - addButton(tr::lng_cancel(), [=, this] { closeBox(); }); - } - - void ConfirmationBox::resizeEvent(QResizeEvent* e) - { - BoxContent::resizeEvent(e); - - const auto& padding = st::boxPadding; - _text->moveToLeft(padding.left(), padding.top()); - } - - void ConfirmationBox::ReadAllPeers() - { - auto settings = &AyuSettings::getInstance(); - auto prev = settings->sendReadMessages; - settings->set_sendReadMessages(true); - - auto chats = _controller->session().data().chatsList(); - Window::MarkAsReadChatListHack(chats); - - settings->set_sendReadMessages(prev); - } +ConfirmationBox::ConfirmationBox( + QWidget *, + not_null controller) + : _controller(controller) +{ + // +} + +void ConfirmationBox::prepare() +{ + _text.create(this, tr::ayu_ReadConfirmationBoxQuestion(), st::boxLabel); + + auto fullHeight = st::boxPadding.top() + + _text->height() + + st::boxPadding.bottom(); + + setDimensions(st::boxWidth, fullHeight); + + addButton(tr::ayu_ReadConfirmationBoxActionText(), [=, this] + { + ReadAllPeers(); + closeBox(); + }); + addButton(tr::lng_cancel(), [=, this] + { closeBox(); }); +} + +void ConfirmationBox::resizeEvent(QResizeEvent *e) +{ + BoxContent::resizeEvent(e); + + const auto &padding = st::boxPadding; + _text->moveToLeft(padding.left(), padding.top()); +} + +void ConfirmationBox::ReadAllPeers() +{ + auto settings = &AyuSettings::getInstance(); + auto prev = settings->sendReadMessages; + settings->set_sendReadMessages(true); + + auto chats = _controller->session().data().chatsList(); + Window::MarkAsReadChatListHack(chats); + + settings->set_sendReadMessages(prev); +} + } diff --git a/Telegram/SourceFiles/ayu/ui/boxes/confirmation_box.h b/Telegram/SourceFiles/ayu/ui/boxes/confirmation_box.h index da98a0ddf..d6f5f0cc3 100644 --- a/Telegram/SourceFiles/ayu/ui/boxes/confirmation_box.h +++ b/Telegram/SourceFiles/ayu/ui/boxes/confirmation_box.h @@ -4,26 +4,29 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 +#pragma once #include "ui/layers/box_content.h" #include "window/window_main_menu.h" namespace AyuUi { - class ConfirmationBox : public Ui::BoxContent - { - public: - ConfirmationBox(QWidget*, not_null controller); - protected: - void prepare() override; +class ConfirmationBox : public Ui::BoxContent +{ +public: + ConfirmationBox(QWidget *, not_null controller); - void resizeEvent(QResizeEvent* e) override; +protected: + void prepare() override; - private: - void ReadAllPeers(); + void resizeEvent(QResizeEvent *e) override; + +private: + void ReadAllPeers(); + + not_null _controller; + object_ptr _text = {nullptr}; +}; - not_null _controller; - object_ptr _text = {nullptr}; - }; } diff --git a/Telegram/SourceFiles/ayu/ui/boxes/edit_deleted_mark.cpp b/Telegram/SourceFiles/ayu/ui/boxes/edit_deleted_mark.cpp index 2b9404d11..2f7305adc 100644 --- a/Telegram/SourceFiles/ayu/ui/boxes/edit_deleted_mark.cpp +++ b/Telegram/SourceFiles/ayu/ui/boxes/edit_deleted_mark.cpp @@ -28,7 +28,8 @@ #include "ayu/ayu_settings.h" -EditDeletedMarkBox::EditDeletedMarkBox(QWidget*) : +EditDeletedMarkBox::EditDeletedMarkBox(QWidget *) + : _text( this, st::defaultInputField, @@ -47,12 +48,16 @@ void EditDeletedMarkBox::prepare() newHeight += st::boxPadding.bottom() + st::contactPadding.bottom(); setDimensions(st::boxWidth, newHeight); - addLeftButton(tr::ayu_BoxActionReset(), [=] { _text->setText(defaultDeletedMark); }); + addLeftButton(tr::ayu_BoxActionReset(), [=] + { _text->setText(defaultDeletedMark); }); - addButton(tr::lng_settings_save(), [=] { save(); }); - addButton(tr::lng_cancel(), [=] { closeBox(); }); + addButton(tr::lng_settings_save(), [=] + { save(); }); + addButton(tr::lng_cancel(), [=] + { closeBox(); }); - connect(_text, &Ui::InputField::submitted, [=] { submit(); }); + connect(_text, &Ui::InputField::submitted, [=] + { submit(); }); } void EditDeletedMarkBox::setInnerFocus() @@ -62,26 +67,24 @@ void EditDeletedMarkBox::setInnerFocus() void EditDeletedMarkBox::submit() { - if (_text->getLastText().trimmed().isEmpty()) - { + if (_text->getLastText().trimmed().isEmpty()) { _text->setFocus(); _text->showError(); } - else - { + else { save(); } } -void EditDeletedMarkBox::resizeEvent(QResizeEvent* e) +void EditDeletedMarkBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _text->resize( width() - - st::boxPadding.left() - - st::newGroupInfoPadding.left() - - st::boxPadding.right(), + - st::boxPadding.left() + - st::newGroupInfoPadding.left() + - st::boxPadding.right(), _text->height()); const auto left = st::boxPadding.left() + st::newGroupInfoPadding.left(); diff --git a/Telegram/SourceFiles/ayu/ui/boxes/edit_deleted_mark.h b/Telegram/SourceFiles/ayu/ui/boxes/edit_deleted_mark.h index 839faf7dc..3e1f76fe5 100644 --- a/Telegram/SourceFiles/ayu/ui/boxes/edit_deleted_mark.h +++ b/Telegram/SourceFiles/ayu/ui/boxes/edit_deleted_mark.h @@ -4,7 +4,6 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once #include "base/timer.h" @@ -14,14 +13,14 @@ class EditDeletedMarkBox : public Ui::BoxContent { public: - EditDeletedMarkBox(QWidget*); + EditDeletedMarkBox(QWidget *); protected: void setInnerFocus() override; void prepare() override; - void resizeEvent(QResizeEvent* e) override; + void resizeEvent(QResizeEvent *e) override; private: void submit(); diff --git a/Telegram/SourceFiles/ayu/ui/boxes/edit_edited_mark.cpp b/Telegram/SourceFiles/ayu/ui/boxes/edit_edited_mark.cpp index 3653124c8..3dc640351 100644 --- a/Telegram/SourceFiles/ayu/ui/boxes/edit_edited_mark.cpp +++ b/Telegram/SourceFiles/ayu/ui/boxes/edit_edited_mark.cpp @@ -28,7 +28,8 @@ #include "ayu/ayu_settings.h" -EditEditedMarkBox::EditEditedMarkBox(QWidget*) : +EditEditedMarkBox::EditEditedMarkBox(QWidget *) + : _text( this, st::defaultInputField, @@ -37,7 +38,6 @@ EditEditedMarkBox::EditEditedMarkBox(QWidget*) : { } - void EditEditedMarkBox::prepare() { const auto defaultEditedMark = tr::lng_edited(tr::now); @@ -48,14 +48,17 @@ void EditEditedMarkBox::prepare() newHeight += st::boxPadding.bottom() + st::contactPadding.bottom(); setDimensions(st::boxWidth, newHeight); - addLeftButton(tr::ayu_BoxActionReset(), [=] { _text->setText(defaultEditedMark); }); - addButton(tr::lng_settings_save(), [=] { save(); }); - addButton(tr::lng_cancel(), [=] { closeBox(); }); + addLeftButton(tr::ayu_BoxActionReset(), [=] + { _text->setText(defaultEditedMark); }); + addButton(tr::lng_settings_save(), [=] + { save(); }); + addButton(tr::lng_cancel(), [=] + { closeBox(); }); - connect(_text, &Ui::InputField::submitted, [=] { submit(); }); + connect(_text, &Ui::InputField::submitted, [=] + { submit(); }); } - void EditEditedMarkBox::setInnerFocus() { _text->setFocusFast(); @@ -63,26 +66,24 @@ void EditEditedMarkBox::setInnerFocus() void EditEditedMarkBox::submit() { - if (_text->getLastText().trimmed().isEmpty()) - { + if (_text->getLastText().trimmed().isEmpty()) { _text->setFocus(); _text->showError(); } - else - { + else { save(); } } -void EditEditedMarkBox::resizeEvent(QResizeEvent* e) +void EditEditedMarkBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); _text->resize( width() - - st::boxPadding.left() - - st::newGroupInfoPadding.left() - - st::boxPadding.right(), + - st::boxPadding.left() + - st::newGroupInfoPadding.left() + - st::boxPadding.right(), _text->height()); const auto left = st::boxPadding.left() + st::newGroupInfoPadding.left(); diff --git a/Telegram/SourceFiles/ayu/ui/boxes/edit_edited_mark.h b/Telegram/SourceFiles/ayu/ui/boxes/edit_edited_mark.h index c34fb20b4..09ac2120d 100644 --- a/Telegram/SourceFiles/ayu/ui/boxes/edit_edited_mark.h +++ b/Telegram/SourceFiles/ayu/ui/boxes/edit_edited_mark.h @@ -4,7 +4,6 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once #include "base/timer.h" @@ -14,14 +13,14 @@ class EditEditedMarkBox : public Ui::BoxContent { public: - EditEditedMarkBox(QWidget*); + EditEditedMarkBox(QWidget *); protected: void setInnerFocus() override; void prepare() override; - void resizeEvent(QResizeEvent* e) override; + void resizeEvent(QResizeEvent *e) override; private: void submit(); diff --git a/Telegram/SourceFiles/ayu/ui/boxes/message_history_box.cpp b/Telegram/SourceFiles/ayu/ui/boxes/message_history_box.cpp index abb088a28..d41a08c3e 100644 --- a/Telegram/SourceFiles/ayu/ui/boxes/message_history_box.cpp +++ b/Telegram/SourceFiles/ayu/ui/boxes/message_history_box.cpp @@ -20,59 +20,58 @@ using namespace Settings; namespace AyuUi { - MessageHistoryBox::MessageHistoryBox(QWidget*, HistoryItem* item) - : _content(this), _scroll(base::make_unique_q(this, st::boxScroll)) - { - setupControls(); - addEditedMessagesToLayout(item); - } - void MessageHistoryBox::setupControls() - { - _content.create(this); +MessageHistoryBox::MessageHistoryBox(QWidget *, HistoryItem *item) + : _content(this), _scroll(base::make_unique_q(this, st::boxScroll)) +{ + setupControls(); + addEditedMessagesToLayout(item); +} - _content->resizeToWidth(st::boxWideWidth); - _content->moveToLeft(0, 0); +void MessageHistoryBox::setupControls() +{ + _content.create(this); - _content->heightValue( - ) | start_to_stream(_contentHeight, _content->lifetime()); + _content->resizeToWidth(st::boxWideWidth); + _content->moveToLeft(0, 0); - _scroll->setOwnedWidget( - object_ptr::fromRaw(_content)); - } + _content->heightValue( + ) | start_to_stream(_contentHeight, _content->lifetime()); - void MessageHistoryBox::resizeEvent(QResizeEvent* e) - { - _scroll->resize(width(), height() - st::boxPhotoPadding.top() - st::boxPadding.bottom()); - _scroll->move(0, st::boxPadding.top()); + _scroll->setOwnedWidget( + object_ptr::fromRaw(_content)); +} - if (_content) - { - _content->resize(_scroll->width(), _content->height()); - } - } +void MessageHistoryBox::resizeEvent(QResizeEvent *e) +{ + _scroll->resize(width(), height() - st::boxPhotoPadding.top() - st::boxPadding.bottom()); + _scroll->move(0, st::boxPadding.top()); - void MessageHistoryBox::prepare() - { - setTitle(tr::ayu_EditsHistoryTitle()); - - setDimensions(st::boxWideWidth, 900); - SetupShadowsToScrollContent(this, _scroll, _contentHeight.events()); - } - - void MessageHistoryBox::addEditedMessagesToLayout(HistoryItem* item) - { - auto messages = AyuMessages::getInstance().getEditedMessages(item); - if (messages.empty()) - { - return; - } - - for (const auto& message : messages) - { - AddSkip(_content); - AddDividerText(_content, rpl::single(QString::fromStdString(message.text))); - AddSkip(_content); - } + if (_content) { + _content->resize(_scroll->width(), _content->height()); } } + +void MessageHistoryBox::prepare() +{ + setTitle(tr::ayu_EditsHistoryTitle()); + + setDimensions(st::boxWideWidth, 900); + SetupShadowsToScrollContent(this, _scroll, _contentHeight.events()); +} + +void MessageHistoryBox::addEditedMessagesToLayout(HistoryItem *item) +{ + auto messages = AyuMessages::getInstance().getEditedMessages(item); + if (messages.empty()) { + return; + } + + for (const auto &message : messages) { + AddSkip(_content); + AddDividerText(_content, rpl::single(QString::fromStdString(message.text))); + AddSkip(_content); + } +} + +} diff --git a/Telegram/SourceFiles/ayu/ui/boxes/message_history_box.h b/Telegram/SourceFiles/ayu/ui/boxes/message_history_box.h index 772da1fe7..b705c0bcf 100644 --- a/Telegram/SourceFiles/ayu/ui/boxes/message_history_box.h +++ b/Telegram/SourceFiles/ayu/ui/boxes/message_history_box.h @@ -4,7 +4,6 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once #include "history/history_item.h" @@ -14,24 +13,26 @@ namespace AyuUi { - class MessageHistoryBox : public Ui::BoxContent - { - public: - MessageHistoryBox(QWidget*, HistoryItem* item); - protected: - void prepare() override; +class MessageHistoryBox : public Ui::BoxContent +{ +public: + MessageHistoryBox(QWidget *, HistoryItem *item); - void resizeEvent(QResizeEvent* e) override; +protected: + void prepare() override; - private: - void setupControls(); + void resizeEvent(QResizeEvent *e) override; - void addEditedMessagesToLayout(HistoryItem* item); +private: + void setupControls(); - object_ptr _content; - const base::unique_qptr _scroll; + void addEditedMessagesToLayout(HistoryItem *item); + + object_ptr _content; + const base::unique_qptr _scroll; + + rpl::event_stream _contentHeight; +}; - rpl::event_stream _contentHeight; - }; } diff --git a/Telegram/SourceFiles/ayu/ui/boxes/voice_confirmation_box.cpp b/Telegram/SourceFiles/ayu/ui/boxes/voice_confirmation_box.cpp index 93353abf1..84c9ae6f2 100644 --- a/Telegram/SourceFiles/ayu/ui/boxes/voice_confirmation_box.cpp +++ b/Telegram/SourceFiles/ayu/ui/boxes/voice_confirmation_box.cpp @@ -14,112 +14,105 @@ namespace AyuUi { - void VoiceConfirmBox(not_null box, Ui::ConfirmBoxArgs&& args) + +void VoiceConfirmBox(not_null box, Ui::ConfirmBoxArgs &&args) +{ + const auto weak = MakeWeak(box); + const auto lifetime = box->lifetime().make_state(); + + v::match(args.text, [](v::null_t) { - const auto weak = MakeWeak(box); - const auto lifetime = box->lifetime().make_state(); + }, [&](auto &&) + { + const auto label = box->addRow( + object_ptr( + box.get(), + v::text::take_marked(std::move(args.text)), + args.labelStyle ? *args.labelStyle : st::boxLabel), + st::boxPadding); + if (args.labelFilter) { + label->setClickHandlerFilter(std::move(args.labelFilter)); + } + }); - v::match(args.text, [](v::null_t) - { - }, [&](auto&&) - { - const auto label = box->addRow( - object_ptr( - box.get(), - v::text::take_marked(std::move(args.text)), - args.labelStyle ? *args.labelStyle : st::boxLabel), - st::boxPadding); - if (args.labelFilter) - { - label->setClickHandlerFilter(std::move(args.labelFilter)); - } - }); - - const auto prepareCallback = [&](Ui::ConfirmBoxArgs::Callback& callback) + const auto prepareCallback = [&](Ui::ConfirmBoxArgs::Callback &callback) + { + return [=, confirmed = std::move(callback)]() { - return [=, confirmed = std::move(callback)]() - { - if (const auto callbackPtr = std::get_if<1>(&confirmed)) - { - if (auto callback = (*callbackPtr)) - { - callback(); - } + if (const auto callbackPtr = std::get_if<1>(&confirmed)) { + if (auto callback = (*callbackPtr)) { + callback(); } - else if (const auto callbackPtr = std::get_if<2>(&confirmed)) - { - if (auto callback = (*callbackPtr)) - { - callback(crl::guard(weak, [=] { weak->closeBox(); })); - } + } + else if (const auto callbackPtr = std::get_if<2>(&confirmed)) { + if (auto callback = (*callbackPtr)) { + callback(crl::guard(weak, [=] + { weak->closeBox(); })); } - else if (weak) - { - weak->closeBox(); - } - }; + } + else if (weak) { + weak->closeBox(); + } }; + }; - const auto& defaultButtonStyle = box->getDelegate()->style().button; + const auto &defaultButtonStyle = box->getDelegate()->style().button; - const auto confirmButton = box->addButton( - v::text::take_plain(std::move(args.confirmText), tr::lng_box_ok()), - [=, c = prepareCallback(args.confirmed)]() + const auto confirmButton = box->addButton( + v::text::take_plain(std::move(args.confirmText), tr::lng_box_ok()), + [=, c = prepareCallback(args.confirmed)]() + { + lifetime->destroy(); + c(); + + weak->closeBox(); + }, + args.confirmStyle ? *args.confirmStyle : defaultButtonStyle); + box->events( + ) | start_with_next([=](not_null e) + { + if ((e->type() != QEvent::KeyPress) || !confirmButton) { + return; + } + const auto k = static_cast(e.get()); + if (k->key() == Qt::Key_Enter || k->key() == Qt::Key_Return) { + confirmButton->clicked(Qt::KeyboardModifiers(), Qt::LeftButton); + } + }, box->lifetime()); + + if (!args.inform) { + const auto cancelButton = box->addButton( + v::text::take_plain(std::move(args.cancelText), tr::lng_cancel()), + crl::guard(weak, [=, c = prepareCallback(args.cancelled)]() { lifetime->destroy(); c(); + }), + args.cancelStyle ? *args.cancelStyle : defaultButtonStyle); - weak->closeBox(); - }, - args.confirmStyle ? *args.confirmStyle : defaultButtonStyle); - box->events( - ) | start_with_next([=](not_null e) + box->boxClosing( + ) | start_with_next(crl::guard(cancelButton, [=] { - if ((e->type() != QEvent::KeyPress) || !confirmButton) - { - return; - } - const auto k = static_cast(e.get()); - if (k->key() == Qt::Key_Enter || k->key() == Qt::Key_Return) - { - confirmButton->clicked(Qt::KeyboardModifiers(), Qt::LeftButton); - } - }, box->lifetime()); - - if (!args.inform) - { - const auto cancelButton = box->addButton( - v::text::take_plain(std::move(args.cancelText), tr::lng_cancel()), - crl::guard(weak, [=, c = prepareCallback(args.cancelled)]() - { - lifetime->destroy(); - c(); - }), - args.cancelStyle ? *args.cancelStyle : defaultButtonStyle); - - box->boxClosing( - ) | start_with_next(crl::guard(cancelButton, [=] - { - cancelButton->clicked(Qt::KeyboardModifiers(), Qt::LeftButton); - }), *lifetime); - } - - if (args.strictCancel) - { - lifetime->destroy(); - } + cancelButton->clicked(Qt::KeyboardModifiers(), Qt::LeftButton); + }), *lifetime); } - object_ptr MakeConfirmBox(Ui::ConfirmBoxArgs&& args) - { - return Box(VoiceConfirmBox, std::move(args)); + if (args.strictCancel) { + lifetime->destroy(); } +} + +object_ptr MakeConfirmBox(Ui::ConfirmBoxArgs &&args) +{ + return Box(VoiceConfirmBox, std::move(args)); +} + +object_ptr MakeInformBox(v::text::data text) +{ + return MakeConfirmBox({ + .text = std::move(text), + .inform = true, + }); +} - object_ptr MakeInformBox(v::text::data text) - { - return MakeConfirmBox({ - .text = std::move(text), - .inform = true, - }); - } } // namespace AyuUi diff --git a/Telegram/SourceFiles/ayu/ui/boxes/voice_confirmation_box.h b/Telegram/SourceFiles/ayu/ui/boxes/voice_confirmation_box.h index 4b8b85ec9..059944976 100644 --- a/Telegram/SourceFiles/ayu/ui/boxes/voice_confirmation_box.h +++ b/Telegram/SourceFiles/ayu/ui/boxes/voice_confirmation_box.h @@ -4,7 +4,6 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once #include "ui/boxes/confirm_box.h" @@ -13,10 +12,12 @@ namespace AyuUi { - void VoiceConfirmBox(not_null box, Ui::ConfirmBoxArgs&& args); - [[nodiscard]] object_ptr MakeConfirmBox( - Ui::ConfirmBoxArgs&& args); +void VoiceConfirmBox(not_null box, Ui::ConfirmBoxArgs &&args); + +[[nodiscard]] object_ptr MakeConfirmBox( + Ui::ConfirmBoxArgs &&args); + +[[nodiscard]] object_ptr MakeInformBox(v::text::data text); - [[nodiscard]] object_ptr MakeInformBox(v::text::data text); } // namespace Ui diff --git a/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.cpp b/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.cpp index 57e4a9a45..099e6c8b3 100644 --- a/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.cpp +++ b/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.cpp @@ -4,11 +4,9 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - -#include "context_menu.h" +#include "ayu/ui/context_menu/context_menu.h" #include "lang_auto.h" #include "ayu/ayu_state.h" -#include "ayu/database/ayu_database.h" #include "ayu/messages/ayu_messages_controller.h" #include "ayu/ui/boxes/message_history_box.h" @@ -17,46 +15,49 @@ #include "styles/style_chat.h" #include "ui/widgets/popup_menu.h" +#include "ayu/ui/sections/edited/edited_log_section.h" + + namespace AyuUi { - AyuPopupMenu::AyuPopupMenu(HistoryInner* parent) - { - _ayuSubMenu = std::make_unique(parent, st::popupMenuWithIcons); - } - void AyuPopupMenu::addHistoryAction(HistoryItem* item) - { - if (AyuMessages::getInstance().hasRevisions(item)) +AyuPopupMenu::AyuPopupMenu(HistoryInner *parent) +{ + _ayuSubMenu = std::make_unique(parent, st::popupMenuWithIcons); +} + +void AyuPopupMenu::addHistoryAction(HistoryItem *item) +{ + if (AyuMessages::getInstance().hasRevisions(item)) { + _ayuSubMenu->addAction(tr::ayu_EditsHistoryMenuText(tr::now), [=] { - _ayuSubMenu->addAction(tr::ayu_EditsHistoryMenuText(tr::now), [=] - { - auto box = Box(item); - show(std::move(box)); - }, &st::menuIconInfo); - } + item->history()->session().tryResolveWindow()->showSection(std::make_shared(item->history()->peer, item)); + }, &st::menuIconInfo); } +} - void AyuPopupMenu::addHideMessageAction(HistoryItem* item) const +void AyuPopupMenu::addHideMessageAction(HistoryItem *item) const +{ + const auto settings = &AyuSettings::getInstance(); + const auto history = item->history(); + _ayuSubMenu->addAction(tr::ayu_ContextHideMessage(tr::now), [=]() { - const auto settings = &AyuSettings::getInstance(); - const auto history = item->history(); - _ayuSubMenu->addAction(tr::ayu_ContextHideMessage(tr::now), [=]() - { - const auto initSaveDeleted = settings->saveDeletedMessages; + const auto initSaveDeleted = settings->saveDeletedMessages; - settings->set_keepDeletedMessages(false); - history->destroyMessage(item); - settings->set_keepDeletedMessages(initSaveDeleted); - }, &st::menuIconClear); - } + settings->set_keepDeletedMessages(false); + history->destroyMessage(item); + settings->set_keepDeletedMessages(initSaveDeleted); + }, &st::menuIconClear); +} - void AyuPopupMenu::addReadUntilAction(HistoryItem* item) const +void AyuPopupMenu::addReadUntilAction(HistoryItem *item) const +{ + const auto history = item->history(); + _ayuSubMenu->addAction(tr::ayu_ReadUntilMenuText(tr::now), [=]() { - const auto history = item->history(); - _ayuSubMenu->addAction(tr::ayu_ReadUntilMenuText(tr::now), [=]() - { - AyuState::setAllowSendReadPacket(true); - history->session().data().histories().readInboxOnNewMessage(item); - }, &st::menuIconShowInChat); - } + AyuState::setAllowSendReadPacket(true); + history->session().data().histories().readInboxOnNewMessage(item); + }, &st::menuIconShowInChat); +} + } // namespace AyuUi diff --git a/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.h b/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.h index 3c3fda82a..6d3e58a82 100644 --- a/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.h +++ b/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.h @@ -4,7 +4,6 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once #include "mainwindow.h" @@ -78,22 +77,24 @@ #include "history/view/history_view_context_menu.h" -#include -#include +#include "ayu/ayu_settings.h" +#include "styles/style_info.h" namespace AyuUi { - class AyuPopupMenu - { - public: - AyuPopupMenu(HistoryInner* parent); - void addHistoryAction(HistoryItem* item); +class AyuPopupMenu +{ +public: + AyuPopupMenu(HistoryInner *parent); - void addHideMessageAction(HistoryItem* item) const; + void addHistoryAction(HistoryItem *item); - void addReadUntilAction(HistoryItem* item) const; + void addHideMessageAction(HistoryItem *item) const; + + void addReadUntilAction(HistoryItem *item) const; + + std::unique_ptr _ayuSubMenu; +}; - std::unique_ptr _ayuSubMenu; - }; } diff --git a/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_inner.cpp b/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_inner.cpp new file mode 100644 index 000000000..085f3336c --- /dev/null +++ b/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_inner.cpp @@ -0,0 +1,1774 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ayu/ui/sections/edited/edited_log_inner.h" + +#include "history/history.h" +#include "history/view/media/history_view_media.h" +#include "history/view/media/history_view_web_page.h" +#include "history/history_item.h" +#include "history/history_item_components.h" +#include "history/history_item_text.h" +#include "ayu/ui/sections/edited/edited_log_section.h" +#include "history/admin_log/history_admin_log_filter.h" +#include "history/view/history_view_message.h" +#include "history/view/history_view_service_message.h" +#include "history/view/history_view_cursor_state.h" +#include "chat_helpers/message_field.h" +#include "boxes/sticker_set_box.h" +#include "ui/boxes/confirm_box.h" +#include "base/platform/base_platform_info.h" +#include "base/qt/qt_key_modifiers.h" +#include "base/unixtime.h" +#include "base/call_delayed.h" +#include "mainwindow.h" +#include "mainwidget.h" +#include "core/application.h" +#include "apiwrap.h" +#include "api/api_chat_participants.h" +#include "api/api_attached_stickers.h" +#include "window/window_session_controller.h" +#include "main/main_session.h" +#include "main/main_session_settings.h" +#include "ui/chat/chat_theme.h" +#include "ui/chat/chat_style.h" +#include "ui/widgets/popup_menu.h" +#include "ui/image/image.h" +#include "ui/text/text_utilities.h" +#include "ui/inactive_press.h" +#include "ui/painter.h" +#include "ui/effects/path_shift_gradient.h" +#include "core/click_handler_types.h" +#include "core/file_utilities.h" +#include "lang/lang_keys.h" +#include "boxes/peers/edit_participant_box.h" +#include "boxes/peers/edit_participants_box.h" +#include "data/data_session.h" +#include "data/data_photo.h" +#include "data/data_photo_media.h" +#include "data/data_document.h" +#include "data/data_media_types.h" +#include "data/data_file_click_handler.h" +#include "data/data_file_origin.h" +#include "data/data_cloud_file.h" +#include "data/data_channel.h" +#include "data/data_user.h" +#include "styles/style_chat.h" +#include "styles/style_menu_icons.h" + +#include +#include +#include + +namespace EditedLog +{ +namespace +{ + +// If we require to support more admins we'll have to rewrite this anyway. +constexpr auto kMaxChannelAdmins = 200; + +constexpr auto kScrollDateHideTimeout = 1000; + +constexpr auto kEventsFirstPage = 20; + +constexpr auto kEventsPerPage = 50; + +constexpr auto kClearUserpicsAfter = 50; + +} // namespace + +template +void InnerWidget::enumerateItems(Method method) +{ + constexpr auto TopToBottom = (direction == EnumItemsDirection::TopToBottom); + + // No displayed messages in this history. + if (_items.empty()) { + return; + } + if (_visibleBottom <= _itemsTop || _itemsTop + _itemsHeight <= _visibleTop) { + return; + } + + auto begin = std::rbegin(_items), end = std::rend(_items); + auto from = TopToBottom ? std::lower_bound(begin, end, _visibleTop, [this](auto &elem, int top) + { + return this->itemTop(elem) + elem->height() <= top; + }) : std::upper_bound(begin, end, _visibleBottom, [this](int bottom, auto &elem) + { + return this->itemTop(elem) + elem->height() >= bottom; + }); + auto wasEnd = (from == end); + if (wasEnd) { + --from; + } + if (TopToBottom) { + Assert(itemTop(from->get()) + from->get()->height() > _visibleTop); + } + else { + Assert(itemTop(from->get()) < _visibleBottom); + } + + while (true) { + auto item = from->get(); + auto itemtop = itemTop(item); + auto itembottom = itemtop + item->height(); + + // Binary search should've skipped all the items that are above / below the visible area. + if (TopToBottom) { + Assert(itembottom > _visibleTop); + } + else { + Assert(itemtop < _visibleBottom); + } + + if (!method(item, itemtop, itembottom)) { + return; + } + + // Skip all the items that are below / above the visible area. + if (TopToBottom) { + if (itembottom >= _visibleBottom) { + return; + } + } + else { + if (itemtop <= _visibleTop) { + return; + } + } + + if (TopToBottom) { + if (++from == end) { + break; + } + } + else { + if (from == begin) { + break; + } + --from; + } + } +} + +template +void InnerWidget::enumerateUserpics(Method method) +{ + // Find and remember the top of an attached messages pack + // -1 means we didn't find an attached to next message yet. + int lowestAttachedItemTop = -1; + + auto userpicCallback = [&](not_null view, int itemtop, int itembottom) + { + // Skip all service messages. + if (view->data()->isService()) { + return true; + } + + if (lowestAttachedItemTop < 0 && view->isAttachedToNext()) { + lowestAttachedItemTop = itemtop + view->marginTop(); + } + + // Call method on a userpic for all messages that have it and for those who are not showing it + // because of their attachment to the next message if they are bottom-most visible. + if (view->displayFromPhoto() || (view->hasFromPhoto() && itembottom >= _visibleBottom)) { + if (lowestAttachedItemTop < 0) { + lowestAttachedItemTop = itemtop + view->marginTop(); + } + // Attach userpic to the bottom of the visible area with the same margin as the last message. + auto userpicMinBottomSkip = st::historyPaddingBottom + st::msgMargin.bottom(); + auto userpicBottom = qMin(itembottom - view->marginBottom(), _visibleBottom - userpicMinBottomSkip); + + // Do not let the userpic go above the attached messages pack top line. + userpicBottom = qMax(userpicBottom, lowestAttachedItemTop + st::msgPhotoSize); + + // Call the template callback function that was passed + // and return if it finished everything it needed. + if (!method(view, userpicBottom - st::msgPhotoSize)) { + return false; + } + } + + // Forget the found top of the pack, search for the next one from scratch. + if (!view->isAttachedToNext()) { + lowestAttachedItemTop = -1; + } + + return true; + }; + + enumerateItems(userpicCallback); +} + +template +void InnerWidget::enumerateDates(Method method) +{ + // Find and remember the bottom of an single-day messages pack + // -1 means we didn't find a same-day with previous message yet. + auto lowestInOneDayItemBottom = -1; + + auto dateCallback = [&](not_null view, int itemtop, int itembottom) + { + const auto item = view->data(); + if (lowestInOneDayItemBottom < 0 && view->isInOneDayWithPrevious()) { + lowestInOneDayItemBottom = itembottom - view->marginBottom(); + } + + // Call method on a date for all messages that have it and for those who are not showing it + // because they are in a one day together with the previous message if they are top-most visible. + if (view->displayDate() || (!item->isEmpty() && itemtop <= _visibleTop)) { + if (lowestInOneDayItemBottom < 0) { + lowestInOneDayItemBottom = itembottom - view->marginBottom(); + } + // Attach date to the top of the visible area with the same margin as it has in service message. + auto dateTop = qMax(itemtop, _visibleTop) + st::msgServiceMargin.top(); + + // Do not let the date go below the single-day messages pack bottom line. + auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); + dateTop = qMin(dateTop, lowestInOneDayItemBottom - dateHeight); + + // Call the template callback function that was passed + // and return if it finished everything it needed. + if (!method(view, itemtop, dateTop)) { + return false; + } + } + + // Forget the found bottom of the pack, search for the next one from scratch. + if (!view->isInOneDayWithPrevious()) { + lowestInOneDayItemBottom = -1; + } + + return true; + }; + + enumerateItems(dateCallback); +} + +InnerWidget::InnerWidget( + QWidget *parent, + not_null controller, + not_null peer, + not_null item) + : RpWidget(parent), + _controller(controller), + _peer(peer), + _item(item), + _history(peer->owner().history(peer)), + _api(&_peer->session().mtp()), + _pathGradient( + HistoryView::MakePathShiftGradient( + controller->chatStyle(), + [=] + { update(); })), + _scrollDateCheck([=] + { scrollDateCheck(); }), + _emptyText( + st::historyAdminLogEmptyWidth + - st::historyAdminLogEmptyPadding.left() + - st::historyAdminLogEmptyPadding.left()) +{ + Window::ChatThemeValueFromPeer( + controller, + peer + ) | rpl::start_with_next([=](std::shared_ptr &&theme) + { + _theme = std::move(theme); + controller->setChatStyleTheme(_theme); + }, lifetime()); + + setMouseTracking(true); + _scrollDateHideTimer.setCallback([=] + { scrollDateHideByTimer(); }); + session().data().viewRepaintRequest( + ) | rpl::start_with_next([=](auto view) + { + if (view->delegate() == this) { + repaintItem(view); + } + }, lifetime()); + session().data().viewResizeRequest( + ) | rpl::start_with_next([=](auto view) + { + if (view->delegate() == this) { + resizeItem(view); + } + }, lifetime()); + session().data().itemViewRefreshRequest( + ) | rpl::start_with_next([=](auto item) + { + if (const auto view = viewForItem(item)) { + refreshItem(view); + } + }, lifetime()); + session().data().viewLayoutChanged( + ) | rpl::start_with_next([=](auto view) + { + if (view->delegate() == this) { + if (view->isUnderCursor()) { + updateSelected(); + } + } + }, lifetime()); + session().data().itemDataChanges( + ) | rpl::start_with_next([=](not_null item) + { + if (const auto view = viewForItem(item)) { + view->itemDataChanged(); + } + }, lifetime()); + session().data().itemVisibilityQueries( + ) | rpl::filter([=]( + const Data::Session::ItemVisibilityQuery &query) + { + return (_history == query.item->history()) + && query.item->isAdminLogEntry() + && isVisible(); + }) | rpl::start_with_next([=]( + const Data::Session::ItemVisibilityQuery &query) + { + if (const auto view = viewForItem(query.item)) { + auto top = itemTop(view); + if (top >= 0 + && top + view->height() > _visibleTop + && top < _visibleBottom) { + *query.isVisible = true; + } + } + }, lifetime()); + + controller->adaptive().chatWideValue( + ) | rpl::start_with_next([=](bool wide) + { + _isChatWide = wide; + }, lifetime()); + + updateEmptyText(); +} + +Main::Session &InnerWidget::session() const +{ + return _controller->session(); +} + +rpl::producer InnerWidget::scrollToSignal() const +{ + return _scrollToSignal.events(); +} + +void InnerWidget::visibleTopBottomUpdated( + int visibleTop, + int visibleBottom) +{ + auto scrolledUp = (visibleTop < _visibleTop); + _visibleTop = visibleTop; + _visibleBottom = visibleBottom; + + // Unload userpics. + if (_userpics.size() > kClearUserpicsAfter) { + _userpicsCache = std::move(_userpics); + } + + updateVisibleTopItem(); + if (_items.size() == 0) { + addEvents(Direction::Up); + } + if (scrolledUp) { + _scrollDateCheck.call(); + } + else { + scrollDateHideByTimer(); + } + _controller->floatPlayerAreaUpdated(); + session().data().itemVisibilitiesUpdated(); +} + +void InnerWidget::updateVisibleTopItem() +{ + if (_visibleBottom == height()) { + _visibleTopItem = nullptr; + } + else { + auto begin = std::rbegin(_items), end = std::rend(_items); + auto from = std::lower_bound(begin, end, _visibleTop, [this](auto &&elem, int top) + { + return this->itemTop(elem) + elem->height() <= top; + }); + if (from != end) { + _visibleTopItem = *from; + _visibleTopFromItem = _visibleTop - _visibleTopItem->y(); + } + else { + _visibleTopItem = nullptr; + _visibleTopFromItem = _visibleTop; + } + } +} + +bool InnerWidget::displayScrollDate() const +{ + return (_visibleTop <= height() - 2 * (_visibleBottom - _visibleTop)); +} + +void InnerWidget::scrollDateCheck() +{ + if (!_visibleTopItem) { + _scrollDateLastItem = nullptr; + _scrollDateLastItemTop = 0; + scrollDateHide(); + } + else if (_visibleTopItem != _scrollDateLastItem || _visibleTopFromItem != _scrollDateLastItemTop) { + // Show scroll date only if it is not the initial onScroll() event (with empty _scrollDateLastItem). + if (_scrollDateLastItem && !_scrollDateShown) { + toggleScrollDateShown(); + } + _scrollDateLastItem = _visibleTopItem; + _scrollDateLastItemTop = _visibleTopFromItem; + _scrollDateHideTimer.callOnce(kScrollDateHideTimeout); + } +} + +void InnerWidget::scrollDateHideByTimer() +{ + _scrollDateHideTimer.cancel(); + scrollDateHide(); +} + +void InnerWidget::scrollDateHide() +{ + if (_scrollDateShown) { + toggleScrollDateShown(); + } +} + +void InnerWidget::toggleScrollDateShown() +{ + _scrollDateShown = !_scrollDateShown; + auto from = _scrollDateShown ? 0. : 1.; + auto to = _scrollDateShown ? 1. : 0.; + _scrollDateOpacity.start([this] + { repaintScrollDateCallback(); }, from, to, st::historyDateFadeDuration); +} + +void InnerWidget::repaintScrollDateCallback() +{ + auto updateTop = _visibleTop; + auto updateHeight = st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->height + + st::msgServicePadding.bottom(); + update(0, updateTop, width(), updateHeight); +} + +void InnerWidget::updateEmptyText() +{ + auto hasSearch = false; + auto hasFilter = false; + auto text = Ui::Text::Semibold((hasSearch || hasFilter) + ? tr::lng_admin_log_no_results_title(tr::now) + : tr::lng_admin_log_no_events_title(tr::now)); + auto description = _peer->isMegagroup() + ? tr::lng_admin_log_no_events_text(tr::now) + : tr::lng_admin_log_no_events_text_channel(tr::now); + text.text.append(u"\n\n"_q + description); + _emptyText.setMarkedText(st::defaultTextStyle, text); +} + +QString InnerWidget::tooltipText() const +{ + if (_mouseCursorState == CursorState::Date + && _mouseAction == MouseAction::None) { + if (const auto view = Element::Hovered()) { + auto dateText = HistoryView::DateTooltipText(view); + + const auto sentIt = _itemDates.find(view->data()); + if (sentIt != end(_itemDates)) { + dateText += '\n' + tr::lng_sent_date( + tr::now, + lt_date, + QLocale().toString( + base::unixtime::parse(sentIt->second), + QLocale::LongFormat)); + } + return dateText; + } + } + else if (_mouseCursorState == CursorState::Forwarded + && _mouseAction == MouseAction::None) { + if (const auto view = Element::Hovered()) { + if (const auto forwarded = view->data()->Get()) { + return forwarded->text.toString(); + } + } + } + else if (const auto lnk = ClickHandler::getActive()) { + return lnk->tooltip(); + } + return QString(); +} + +QPoint InnerWidget::tooltipPos() const +{ + return _mousePosition; +} + +bool InnerWidget::tooltipWindowActive() const +{ + return Ui::AppInFocus() && Ui::InFocusChain(window()); +} + +HistoryView::Context InnerWidget::elementContext() +{ + return HistoryView::Context::AdminLog; +} + +bool InnerWidget::elementUnderCursor( + not_null view) +{ + return (Element::Hovered() == view); +} + +float64 InnerWidget::elementHighlightOpacity( + not_null item) const +{ + return 0.; +} + +bool InnerWidget::elementInSelectionMode() +{ + return false; +} + +bool InnerWidget::elementIntersectsRange( + not_null view, + int from, + int till) +{ + Expects(view->delegate() == this); + + const auto top = itemTop(view); + const auto bottom = top + view->height(); + return (top < till && bottom > from); +} + +void InnerWidget::elementStartStickerLoop(not_null view) +{ +} + +void InnerWidget::elementShowPollResults( + not_null poll, + FullMsgId context) +{ +} + +void InnerWidget::elementOpenPhoto( + not_null photo, + FullMsgId context) +{ + _controller->openPhoto(photo, {context}); +} + +void InnerWidget::elementOpenDocument( + not_null document, + FullMsgId context, + bool showInMediaView) +{ + _controller->openDocument(document, showInMediaView, {context}); +} + +void InnerWidget::elementCancelUpload(const FullMsgId &context) +{ + if (const auto item = session().data().message(context)) { + _controller->cancelUploadLayer(item); + } +} + +void InnerWidget::elementShowTooltip( + const TextWithEntities &text, + Fn hiddenCallback) +{ +} + +bool InnerWidget::elementAnimationsPaused() +{ + return _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Any); +} + +bool InnerWidget::elementHideReply(not_null view) +{ + return true; +} + +bool InnerWidget::elementShownUnread(not_null view) +{ + return false; +} + +void InnerWidget::elementSendBotCommand( + const QString &command, + const FullMsgId &context) +{ +} + +void InnerWidget::elementHandleViaClick(not_null bot) +{ +} + +bool InnerWidget::elementIsChatWide() +{ + return _isChatWide; +} + +not_null InnerWidget::elementPathShiftGradient() +{ + return _pathGradient.get(); +} + +void InnerWidget::elementReplyTo(const FullMsgId &to) +{ +} + +void InnerWidget::elementStartInteraction(not_null view) +{ +} + +void InnerWidget::elementStartPremium( + not_null view, + Element *replacing) +{ +} + +void InnerWidget::elementCancelPremium(not_null view) +{ +} + +QString InnerWidget::elementAuthorRank(not_null view) +{ + return {}; +} + +void InnerWidget::saveState(not_null memento) +{ + if (!_filterChanged) { + for (auto &item : _items) { + item.clearView(); + } + memento->setItems( + base::take(_items), + base::take(_eventIds), + _upLoaded, + _downLoaded); + base::take(_itemsByData); + } + _upLoaded = _downLoaded = true; // Don't load or handle anything anymore. +} + +void InnerWidget::restoreState(not_null memento) +{ + _items = memento->takeItems(); + for (auto &item : _items) { + item.refreshView(this); + _itemsByData.emplace(item->data(), item.get()); + } + _eventIds = memento->takeEventIds(); + _upLoaded = memento->upLoaded(); + _downLoaded = memento->downLoaded(); + _filterChanged = false; + updateSize(); +} + +void InnerWidget::addEvents(Direction direction) +{ + auto messages = AyuMessages::getInstance().getEditedMessages(_item); + if (messages.empty()) { + return; + } + + const auto size = messages.size(); + auto &container = _items; + + for (const auto &message : messages) { + const auto addOne = [&]( + OwnedItem item, + TimeId sentDate, + MsgId realId) + { + if (sentDate) { + _itemDates.emplace(item->data(), sentDate); + } + _itemsByData.emplace(item->data(), item.get()); + container.push_back(std::move(item)); + }; + GenerateItems( + this, + _history, + message, + addOne); + } + + itemsAdded(direction, size); + update(); + repaint(); +} + +void InnerWidget::itemsAdded(Direction direction, int addedCount) +{ + Expects(addedCount >= 0); + auto checkFrom = (direction == Direction::Up) + ? (_items.size() - addedCount) + : 1; // Should be ": 0", but zero is skipped anyway. + auto checkTo = (direction == Direction::Up) ? (_items.size() + 1) : (addedCount + 1); + for (auto i = checkFrom; i != checkTo; ++i) { + if (i > 0) { + const auto view = _items[i - 1].get(); + if (i < _items.size()) { + const auto previous = _items[i].get(); + view->setDisplayDate(view->dateTime().date() != previous->dateTime().date()); + const auto attach = view->computeIsAttachToPrevious(previous); + view->setAttachToPrevious(attach, previous); + previous->setAttachToNext(attach, view); + } + else { + view->setDisplayDate(true); + } + } + } + updateSize(); +} + +void InnerWidget::updateSize() +{ + TWidget::resizeToWidth(width()); + restoreScrollPosition(); + updateVisibleTopItem(); +} + +int InnerWidget::resizeGetHeight(int newWidth) +{ + update(); + + const auto resizeAllItems = (_itemsWidth != newWidth); + auto newHeight = 0; + for (const auto &item : ranges::views::reverse(_items)) { + item->setY(newHeight); + if (item->pendingResize() || resizeAllItems) { + newHeight += item->resizeGetHeight(newWidth); + } + else { + newHeight += item->height(); + } + } + _itemsWidth = newWidth; + _itemsHeight = newHeight; + _itemsTop = + (_minHeight > _itemsHeight + st::historyPaddingBottom) ? (_minHeight - _itemsHeight - st::historyPaddingBottom) + : 0; + return _itemsTop + _itemsHeight + st::historyPaddingBottom; +} + +void InnerWidget::restoreScrollPosition() +{ + const auto newVisibleTop = _visibleTopItem + ? (itemTop(_visibleTopItem) + _visibleTopFromItem) + : ScrollMax; + _scrollToSignal.fire_copy(newVisibleTop); +} + +void InnerWidget::paintEvent(QPaintEvent *e) +{ + if (_controller->contentOverlapped(this, e)) { + return; + } + + const auto guard = gsl::finally([&] + { + _userpicsCache.clear(); + }); + + Painter p(this); + + auto clip = e->rect(); + auto context = _controller->preparePaintContext({ + .theme = _theme.get(), + .visibleAreaTop = _visibleTop, + .visibleAreaTopGlobal = mapToGlobal(QPoint(0, _visibleTop)).y(), + .visibleAreaWidth = width(), + .clip = clip, + }); + if (_items.empty() && _upLoaded && _downLoaded) { + paintEmpty(p, context.st); + } + else { + _pathGradient->startFrame( + 0, + width(), + std::min(st::msgMaxWidth / 2, width() / 2)); + + auto begin = std::rbegin(_items), end = std::rend(_items); + auto from = std::lower_bound(begin, end, clip.top(), [this](auto &elem, int top) + { + return this->itemTop(elem) + elem->height() <= top; + }); + auto to = std::lower_bound(begin, end, clip.top() + clip.height(), [this](auto &elem, int bottom) + { + return this->itemTop(elem) < bottom; + }); + if (from != end) { + auto top = itemTop(from->get()); + context.translate(0, -top); + p.translate(0, top); + for (auto i = from; i != to; ++i) { + const auto view = i->get(); + context.outbg = view->hasOutLayout(); + context.selection = (view == _selectedItem) + ? _selectedText + : TextSelection(); + view->draw(p, context); + + const auto height = view->height(); + top += height; + context.translate(0, -height); + p.translate(0, height); + } + context.translate(0, top); + p.translate(0, -top); + + enumerateUserpics([&](not_null view, int userpicTop) + { + // stop the enumeration if the userpic is below the painted rect + if (userpicTop >= clip.top() + clip.height()) { + return false; + } + + // paint the userpic if it intersects the painted rect + if (userpicTop + st::msgPhotoSize > clip.top()) { + const auto from = view->data()->from(); + from->paintUserpicLeft( + p, + _userpics[from], + st::historyPhotoLeft, + userpicTop, + view->width(), + st::msgPhotoSize); + } + return true; + }); + + auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); + auto scrollDateOpacity = _scrollDateOpacity.value(_scrollDateShown ? 1. : 0.); + enumerateDates([&](not_null view, int itemtop, int dateTop) + { + // stop the enumeration if the date is above the painted rect + if (dateTop + dateHeight <= clip.top()) { + return false; + } + + const auto displayDate = view->displayDate(); + auto dateInPlace = displayDate; + if (dateInPlace) { + const auto correctDateTop = itemtop + st::msgServiceMargin.top(); + dateInPlace = (dateTop < correctDateTop + dateHeight); + } + //bool noFloatingDate = (item->date.date() == lastDate && displayDate); + //if (noFloatingDate) { + // if (itemtop < showFloatingBefore) { + // noFloatingDate = false; + // } + //} + + // paint the date if it intersects the painted rect + if (dateTop < clip.top() + clip.height()) { + auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity; + if (opacity > 0.) { + p.setOpacity(opacity); + const auto dateY = /*noFloatingDate ? itemtop :*/ + (dateTop - st::msgServiceMargin.top()); + const auto width = view->width(); + if (const auto date = view->Get()) { + date->paint(p, context.st, dateY, width, _isChatWide); + } + else { + HistoryView::ServiceMessagePainter::PaintDate( + p, + context.st, + view->dateTime(), + dateY, + width, + _isChatWide); + } + } + } + return true; + }); + } + } +} + +auto InnerWidget::viewForItem(const HistoryItem *item) -> Element * +{ + if (item) { + const auto i = _itemsByData.find(item); + if (i != _itemsByData.end()) { + return i->second; + } + } + return nullptr; +} + +void InnerWidget::paintEmpty(Painter &p, not_null st) +{ + auto rectWidth = st::historyAdminLogEmptyWidth; + auto innerWidth = rectWidth - st::historyAdminLogEmptyPadding.left() - st::historyAdminLogEmptyPadding.right(); + auto rectHeight = st::historyAdminLogEmptyPadding.top() + _emptyText.countHeight(innerWidth) + + st::historyAdminLogEmptyPadding.bottom(); + auto rect = QRect((width() - rectWidth) / 2, (height() - rectHeight) / 3, rectWidth, rectHeight); + HistoryView::ServiceMessagePainter::PaintBubble(p, st, rect); + + p.setPen(st->msgServiceFg()); + _emptyText.draw(p, + rect.x() + st::historyAdminLogEmptyPadding.left(), + rect.y() + st::historyAdminLogEmptyPadding.top(), + innerWidth, + style::al_top); +} + +TextForMimeData InnerWidget::getSelectedText() const +{ + return _selectedItem + ? _selectedItem->selectedText(_selectedText) + : TextForMimeData(); +} + +void InnerWidget::keyPressEvent(QKeyEvent *e) +{ + if (e == QKeySequence::Copy && _selectedItem != nullptr) { + copySelectedText(); +#ifdef Q_OS_MAC + } else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) { + TextUtilities::SetClipboardText(getSelectedText(), QClipboard::FindBuffer); +#endif // Q_OS_MAC + } + else { + e->ignore(); + } +} + +void InnerWidget::mouseDoubleClickEvent(QMouseEvent *e) +{ + mouseActionStart(e->globalPos(), e->button()); + if (((_mouseAction == MouseAction::Selecting && _selectedItem != nullptr) || (_mouseAction == MouseAction::None)) + && _mouseSelectType == TextSelectType::Letters && _mouseActionItem) { + StateRequest request; + request.flags |= Ui::Text::StateRequest::Flag::LookupSymbol; + auto dragState = _mouseActionItem->textState(_dragStartPosition, request); + if (dragState.cursor == CursorState::Text) { + _mouseTextSymbol = dragState.symbol; + _mouseSelectType = TextSelectType::Words; + if (_mouseAction == MouseAction::None) { + _mouseAction = MouseAction::Selecting; + auto selection = TextSelection{dragState.symbol, dragState.symbol}; + repaintItem(std::exchange(_selectedItem, _mouseActionItem)); + _selectedText = selection; + } + mouseMoveEvent(e); + + _trippleClickPoint = e->globalPos(); + _trippleClickTimer.callOnce(QApplication::doubleClickInterval()); + } + } +} + +void InnerWidget::contextMenuEvent(QContextMenuEvent *e) +{ + showContextMenu(e); +} + +void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) +{ + if (e->reason() == QContextMenuEvent::Mouse) { + mouseActionUpdate(e->globalPos()); + } + + // -1 - has selection, but no over, 0 - no selection, 1 - over text + auto isUponSelected = 0; + auto hasSelected = 0; + if (_selectedItem) { + isUponSelected = -1; + + auto selFrom = _selectedText.from; + auto selTo = _selectedText.to; + hasSelected = (selTo > selFrom) ? 1 : 0; + if (Element::Moused() && Element::Moused() == Element::Hovered()) { + auto mousePos = mapPointToItem( + mapFromGlobal(_mousePosition), + Element::Moused()); + StateRequest request; + request.flags |= Ui::Text::StateRequest::Flag::LookupSymbol; + auto dragState = Element::Moused()->textState(mousePos, request); + if (dragState.cursor == CursorState::Text + && base::in_range(dragState.symbol, selFrom, selTo)) { + isUponSelected = 1; + } + } + } + if (showFromTouch && hasSelected && isUponSelected < hasSelected) { + isUponSelected = hasSelected; + } + + _menu = base::make_unique_q( + this, + st::popupMenuExpandedSeparator); + + const auto link = ClickHandler::getActive(); + auto view = Element::Hovered() + ? Element::Hovered() + : Element::HoveredLink(); + const auto lnkPhoto = link + ? reinterpret_cast( + link->property(kPhotoLinkMediaProperty).toULongLong()) + : nullptr; + const auto lnkDocument = link + ? reinterpret_cast( + link->property(kDocumentLinkMediaProperty).toULongLong()) + : nullptr; + auto lnkIsVideo = lnkDocument ? lnkDocument->isVideoFile() : false; + auto lnkIsVoice = lnkDocument ? lnkDocument->isVoiceMessage() : false; + auto lnkIsAudio = lnkDocument ? lnkDocument->isAudioFile() : false; + const auto fromId = PeerId(link + ? link->property(kPeerLinkPeerIdProperty).toULongLong() + : 0); + if (lnkPhoto || lnkDocument) { + if (isUponSelected > 0) { + _menu->addAction(tr::lng_context_copy_selected(tr::now), [=] + { + copySelectedText(); + }, &st::menuIconCopy); + } + if (lnkPhoto) { + const auto media = lnkPhoto->activeMediaView(); + if (!lnkPhoto->isNull() && media && media->loaded()) { + _menu->addAction(tr::lng_context_save_image(tr::now), + base::fn_delayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [=] + { + savePhotoToFile(lnkPhoto); + }), + &st::menuIconSaveImage); + _menu->addAction(tr::lng_context_copy_image(tr::now), [=] + { + copyContextImage(lnkPhoto); + }, &st::menuIconCopy); + } + if (lnkPhoto->hasAttachedStickers()) { + const auto controller = _controller; + auto callback = [=] + { + auto &attached = session().api().attachedStickers(); + attached.requestAttachedStickerSets(controller, lnkPhoto); + }; + _menu->addAction( + tr::lng_context_attached_stickers(tr::now), + std::move(callback), + &st::menuIconStickers); + } + } + else { + if (lnkDocument->loading()) { + _menu->addAction(tr::lng_context_cancel_download(tr::now), [=] + { + cancelContextDownload(lnkDocument); + }, &st::menuIconCancel); + } + else { + const auto itemId = view + ? view->data()->fullId() + : FullMsgId(); + if (const auto item = session().data().message(itemId)) { + const auto notAutoplayedGif = [&] + { + return lnkDocument->isGifv() + && !Data::AutoDownload::ShouldAutoPlay( + session().settings().autoDownload(), + item->history()->peer, + lnkDocument); + }(); + if (notAutoplayedGif) { + _menu->addAction(tr::lng_context_open_gif(tr::now), [=] + { + openContextGif(itemId); + }, &st::menuIconShowInChat); + } + } + if (!lnkDocument->filepath(true).isEmpty()) { + _menu->addAction(Platform::IsMac() ? tr::lng_context_show_in_finder(tr::now) + : tr::lng_context_show_in_folder(tr::now), [=] + { + showContextInFolder(lnkDocument); + }, &st::menuIconShowInFolder); + } + _menu->addAction(lnkIsVideo ? tr::lng_context_save_video(tr::now) : (lnkIsVoice + ? tr::lng_context_save_audio(tr::now) + : (lnkIsAudio + ? tr::lng_context_save_audio_file( + tr::now) : tr::lng_context_save_file(tr::now))), + base::fn_delayed(st::defaultDropdownMenu.menu.ripple.hideDuration, + this, + [this, lnkDocument] + { + saveDocumentToFile(lnkDocument); + }), + &st::menuIconDownload); + if (lnkDocument->hasAttachedStickers()) { + const auto controller = _controller; + auto callback = [=] + { + auto &attached = session().api().attachedStickers(); + attached.requestAttachedStickerSets(controller, lnkDocument); + }; + _menu->addAction( + tr::lng_context_attached_stickers(tr::now), + std::move(callback), + &st::menuIconStickers); + } + } + } + } + else if (fromId) { // suggest to block +// if (const auto participant = session().data().peer(fromId)) { +// suggestRestrictParticipant(participant); +// } + } + else { // maybe cursor on some text history item? + const auto item = view ? view->data().get() : nullptr; + const auto itemId = item ? item->fullId() : FullMsgId(); + + if (isUponSelected > 0) { + _menu->addAction( + tr::lng_context_copy_selected(tr::now), + [this] + { copySelectedText(); }, + &st::menuIconCopy); + } + else { + if (item && !isUponSelected) { + const auto media = view->media(); + const auto mediaHasTextForCopy = media && media->hasTextForCopy(); + if (const auto document = media ? media->getDocument() : nullptr) { + if (document->sticker()) { + _menu->addAction(tr::lng_context_save_image(tr::now), + base::fn_delayed(st::defaultDropdownMenu.menu.ripple.hideDuration, + this, + [this, document] + { + saveDocumentToFile(document); + }), + &st::menuIconDownload); + } + } + if (!item->isService() + && !link + && (view->hasVisibleText() + || mediaHasTextForCopy + || item->Has())) { + _menu->addAction(tr::lng_context_copy_text(tr::now), [=] + { + copyContextText(itemId); + }, &st::menuIconCopy); + } + } + } + + const auto actionText = link + ? link->copyToClipboardContextItemText() + : QString(); + if (!actionText.isEmpty()) { + _menu->addAction( + actionText, + [text = link->copyToClipboardText()] + { + QGuiApplication::clipboard()->setText(text); + }, + &st::menuIconCopy); + } + } + + if (_menu->empty()) { + _menu = nullptr; + } + else { + _menu->popup(e->globalPos()); + e->accept(); + } +} + +void InnerWidget::savePhotoToFile(not_null photo) +{ + const auto media = photo->activeMediaView(); + if (photo->isNull() || !media || !media->loaded()) { + return; + } + + auto filter = u"JPEG Image (*.jpg);;"_q + FileDialog::AllFilesFilter(); + FileDialog::GetWritePath( + this, + tr::lng_save_photo(tr::now), + filter, + filedialogDefaultName(u"photo"_q, u".jpg"_q), + crl::guard(this, [=](const QString &result) + { + if (!result.isEmpty()) { + media->saveToFile(result); + } + })); +} + +void InnerWidget::saveDocumentToFile(not_null document) +{ + DocumentSaveClickHandler::Save( + Data::FileOrigin(), + document, + DocumentSaveClickHandler::Mode::ToNewFile); +} + +void InnerWidget::copyContextImage(not_null photo) +{ + const auto media = photo->activeMediaView(); + if (photo->isNull() || !media || !media->loaded()) { + return; + } + media->setToClipboard(); +} + +void InnerWidget::copySelectedText() +{ + TextUtilities::SetClipboardText(getSelectedText()); +} + +void InnerWidget::showStickerPackInfo(not_null document) +{ + StickerSetBox::Show(_controller->uiShow(), document); +} + +void InnerWidget::cancelContextDownload(not_null document) +{ + document->cancel(); +} + +void InnerWidget::showContextInFolder(not_null document) +{ + const auto filepath = document->filepath(true); + if (!filepath.isEmpty()) { + File::ShowInFolder(filepath); + } +} + +void InnerWidget::openContextGif(FullMsgId itemId) +{ + if (const auto item = session().data().message(itemId)) { + if (const auto media = item->media()) { + if (const auto document = media->document()) { + _controller->openDocument(document, true, {itemId}); + } + } + } +} + +void InnerWidget::copyContextText(FullMsgId itemId) +{ + if (const auto item = session().data().message(itemId)) { + TextUtilities::SetClipboardText(HistoryItemText(item)); + } +} + +void InnerWidget::mousePressEvent(QMouseEvent *e) +{ + if (_menu) { + e->accept(); + return; // ignore mouse press, that was hiding context menu + } + mouseActionStart(e->globalPos(), e->button()); +} + +void InnerWidget::mouseMoveEvent(QMouseEvent *e) +{ + auto buttonsPressed = (e->buttons() & (Qt::LeftButton | Qt::MiddleButton)); + if (!buttonsPressed && _mouseAction != MouseAction::None) { + mouseReleaseEvent(e); + } + mouseActionUpdate(e->globalPos()); +} + +void InnerWidget::mouseReleaseEvent(QMouseEvent *e) +{ + mouseActionFinish(e->globalPos(), e->button()); + if (!rect().contains(e->pos())) { + leaveEvent(e); + } +} + +void InnerWidget::enterEventHook(QEnterEvent *e) +{ + mouseActionUpdate(QCursor::pos()); + return TWidget::enterEventHook(e); +} + +void InnerWidget::leaveEventHook(QEvent *e) +{ + if (const auto view = Element::Hovered()) { + repaintItem(view); + Element::Hovered(nullptr); + } + ClickHandler::clearActive(); + Ui::Tooltip::Hide(); + if (!ClickHandler::getPressed() && _cursor != style::cur_default) { + _cursor = style::cur_default; + setCursor(_cursor); + } + return TWidget::leaveEventHook(e); +} + +void InnerWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton button) +{ + mouseActionUpdate(screenPos); + if (button != Qt::LeftButton) return; + + ClickHandler::pressed(); + if (Element::Pressed() != Element::Hovered()) { + repaintItem(Element::Pressed()); + Element::Pressed(Element::Hovered()); + repaintItem(Element::Pressed()); + } + + _mouseAction = MouseAction::None; + _mouseActionItem = Element::Moused(); + _dragStartPosition = mapPointToItem( + mapFromGlobal(screenPos), + _mouseActionItem); + _pressWasInactive = Ui::WasInactivePress(_controller->widget()); + if (_pressWasInactive) { + Ui::MarkInactivePress(_controller->widget(), false); + } + + if (ClickHandler::getPressed()) { + _mouseAction = MouseAction::PrepareDrag; + } + if (_mouseAction == MouseAction::None && _mouseActionItem) { + TextState dragState; + if (_trippleClickTimer.isActive() + && (screenPos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) { + StateRequest request; + request.flags = Ui::Text::StateRequest::Flag::LookupSymbol; + dragState = _mouseActionItem->textState(_dragStartPosition, request); + if (dragState.cursor == CursorState::Text) { + auto selection = TextSelection{dragState.symbol, dragState.symbol}; + repaintItem(std::exchange(_selectedItem, _mouseActionItem)); + _selectedText = selection; + _mouseTextSymbol = dragState.symbol; + _mouseAction = MouseAction::Selecting; + _mouseSelectType = TextSelectType::Paragraphs; + mouseActionUpdate(_mousePosition); + _trippleClickTimer.callOnce(QApplication::doubleClickInterval()); + } + } + else if (Element::Pressed()) { + StateRequest request; + request.flags = Ui::Text::StateRequest::Flag::LookupSymbol; + dragState = _mouseActionItem->textState(_dragStartPosition, request); + } + if (_mouseSelectType != TextSelectType::Paragraphs) { + if (Element::Pressed()) { + _mouseTextSymbol = dragState.symbol; + auto uponSelected = (dragState.cursor == CursorState::Text); + if (uponSelected) { + if (!_selectedItem || _selectedItem != _mouseActionItem) { + uponSelected = false; + } + else if (_mouseTextSymbol < _selectedText.from || _mouseTextSymbol >= _selectedText.to) { + uponSelected = false; + } + } + if (uponSelected) { + _mouseAction = MouseAction::PrepareDrag; // start text drag + } + else if (!_pressWasInactive) { + if (dragState.afterSymbol) ++_mouseTextSymbol; + auto selection = TextSelection{_mouseTextSymbol, _mouseTextSymbol}; + repaintItem(std::exchange(_selectedItem, _mouseActionItem)); + _selectedText = selection; + _mouseAction = MouseAction::Selecting; + repaintItem(_mouseActionItem); + } + } + } + } + + if (!_mouseActionItem) { + _mouseAction = MouseAction::None; + } + else if (_mouseAction == MouseAction::None) { + _mouseActionItem = nullptr; + } +} + +void InnerWidget::mouseActionUpdate(const QPoint &screenPos) +{ + _mousePosition = screenPos; + updateSelected(); +} + +void InnerWidget::mouseActionCancel() +{ + _mouseActionItem = nullptr; + _mouseAction = MouseAction::None; + _dragStartPosition = QPoint(0, 0); + _wasSelectedText = false; + //_widget->noSelectingScroll(); // TODO +} + +void InnerWidget::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button) +{ + mouseActionUpdate(screenPos); + + auto activated = ClickHandler::unpressed(); + if (_mouseAction == MouseAction::Dragging) { + activated = nullptr; + } + if (const auto view = Element::Pressed()) { + repaintItem(view); + Element::Pressed(nullptr); + } + + _wasSelectedText = false; + + if (activated) { + mouseActionCancel(); + ActivateClickHandler(window(), activated, { + button, + QVariant::fromValue(ClickHandlerContext{ + .elementDelegate = [weak = Ui::MakeWeak(this)] + { + return weak + ? (ElementDelegate *)weak + : nullptr; + }, + .sessionWindow = base::make_weak(_controller), + }) + }); + return; + } + if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && button != Qt::RightButton) { + repaintItem(base::take(_selectedItem)); + } + else if (_mouseAction == MouseAction::Selecting) { + if (_selectedItem && !_pressWasInactive) { + if (_selectedText.from == _selectedText.to) { + _selectedItem = nullptr; + _controller->widget()->setInnerFocus(); + } + } + } + _mouseAction = MouseAction::None; + _mouseActionItem = nullptr; + _mouseSelectType = TextSelectType::Letters; + //_widget->noSelectingScroll(); // TODO + + if (QGuiApplication::clipboard()->supportsSelection() + && _selectedItem + && _selectedText.from != _selectedText.to) { + TextUtilities::SetClipboardText( + _selectedItem->selectedText(_selectedText), + QClipboard::Selection); + } +} + +void InnerWidget::updateSelected() +{ + auto mousePosition = mapFromGlobal(_mousePosition); + auto point = QPoint( + std::clamp(mousePosition.x(), 0, width()), + std::clamp(mousePosition.y(), _visibleTop, _visibleBottom)); + + auto itemPoint = QPoint(); + auto begin = std::rbegin(_items), end = std::rend(_items); + auto from = (point.y() >= _itemsTop && point.y() < _itemsTop + _itemsHeight) + ? std::lower_bound(begin, end, point.y(), [this](auto &elem, int top) + { + return this->itemTop(elem) + elem->height() <= top; + }) + : end; + const auto view = (from != end) ? from->get() : nullptr; + const auto item = view ? view->data().get() : nullptr; + if (item) { + Element::Moused(view); + itemPoint = mapPointToItem(point, view); + if (view->pointState(itemPoint) != PointState::Outside) { + if (Element::Hovered() != view) { + repaintItem(Element::Hovered()); + Element::Hovered(view); + repaintItem(view); + } + } + else if (const auto view = Element::Hovered()) { + repaintItem(view); + Element::Hovered(nullptr); + } + } + + TextState dragState; + ClickHandlerHost *lnkhost = nullptr; + auto selectingText = _selectedItem + && (view == _mouseActionItem) + && (view == Element::Hovered()); + if (view) { + if (view != _mouseActionItem + || (itemPoint - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) { + if (_mouseAction == MouseAction::PrepareDrag) { + _mouseAction = MouseAction::Dragging; + InvokeQueued(this, [this] + { performDrag(); }); + } + } + StateRequest request; + if (_mouseAction == MouseAction::Selecting) { + request.flags |= Ui::Text::StateRequest::Flag::LookupSymbol; + } + else { + selectingText = false; + } + if (base::IsAltPressed()) { + request.flags &= ~Ui::Text::StateRequest::Flag::LookupLink; + } + dragState = view->textState(itemPoint, request); + lnkhost = view; + if (!dragState.link && itemPoint.x() >= st::historyPhotoLeft + && itemPoint.x() < st::historyPhotoLeft + st::msgPhotoSize) { + if (!item->isService() && view->hasFromPhoto()) { + enumerateUserpics([&](not_null view, int userpicTop) + { + // stop enumeration if the userpic is below our point + if (userpicTop > point.y()) { + return false; + } + + // stop enumeration if we've found a userpic under the cursor + if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) { + dragState.link = view->data()->from()->openLink(); + lnkhost = view; + return false; + } + return true; + }); + } + } + } + auto lnkChanged = ClickHandler::setActive(dragState.link, lnkhost); + if (lnkChanged || dragState.cursor != _mouseCursorState) { + Ui::Tooltip::Hide(); + } + if (dragState.link + || dragState.cursor == CursorState::Date + || dragState.cursor == CursorState::Forwarded) { + Ui::Tooltip::Show(1000, this); + } + + auto cursor = style::cur_default; + if (_mouseAction == MouseAction::None) { + _mouseCursorState = dragState.cursor; + if (dragState.link) { + cursor = style::cur_pointer; + } + else if (_mouseCursorState == CursorState::Text) { + cursor = style::cur_text; + } + else if (_mouseCursorState == CursorState::Date) { +// cursor = style::cur_cross; + } + } + else if (item) { + if (_mouseAction == MouseAction::Selecting) { + if (selectingText) { + auto second = dragState.symbol; + if (dragState.afterSymbol && _mouseSelectType == TextSelectType::Letters) { + ++second; + } + auto selection = TextSelection{qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol)}; + if (_mouseSelectType != TextSelectType::Letters) { + selection = _mouseActionItem->adjustSelection( + selection, + _mouseSelectType); + } + if (_selectedText != selection) { + _selectedText = selection; + repaintItem(_mouseActionItem); + } + if (!_wasSelectedText && (selection.from != selection.to)) { + _wasSelectedText = true; + setFocus(); + } + } + } + else if (_mouseAction == MouseAction::Dragging) { + } + + if (ClickHandler::getPressed()) { + cursor = style::cur_pointer; + } + else if (_mouseAction == MouseAction::Selecting && _selectedItem) { + cursor = style::cur_text; + } + } + + // Voice message seek support. + if (const auto pressedView = Element::PressedLink()) { + const auto adjustedPoint = mapPointToItem(point, pressedView); + pressedView->updatePressed(adjustedPoint); + } + + //if (_mouseAction == MouseAction::Selecting) { + // _widget->checkSelectingScroll(mousePos); + //} else { + // _widget->noSelectingScroll(); + //} // TODO + + if (_mouseAction == MouseAction::None && (lnkChanged || cursor != _cursor)) { + setCursor(_cursor = cursor); + } +} + +void InnerWidget::performDrag() +{ + if (_mouseAction != MouseAction::Dragging) return; + + //auto uponSelected = false; + //if (_mouseActionItem) { + // if (!_selected.isEmpty() && _selected.cbegin().value() == FullSelection) { + // uponSelected = _selected.contains(_mouseActionItem); + // } else { + // StateRequest request; + // request.flags |= Ui::Text::StateRequest::Flag::LookupSymbol; + // auto dragState = _mouseActionItem->textState(_dragStartPosition.x(), _dragStartPosition.y(), request); + // uponSelected = (dragState.cursor == CursorState::Text); + // if (uponSelected) { + // if (_selected.isEmpty() || + // _selected.cbegin().value() == FullSelection || + // _selected.cbegin().key() != _mouseActionItem + // ) { + // uponSelected = false; + // } else { + // uint16 selFrom = _selected.cbegin().value().from, selTo = _selected.cbegin().value().to; + // if (dragState.symbol < selFrom || dragState.symbol >= selTo) { + // uponSelected = false; + // } + // } + // } + // } + //} + //auto pressedHandler = ClickHandler::getPressed(); + + //if (dynamic_cast(pressedHandler.data())) { + // return; + //} + + //TextWithEntities sel; + //QList urls; + //if (uponSelected) { + // sel = getSelectedText(); + //} else if (pressedHandler) { + // sel = { pressedHandler->dragText(), EntitiesInText() }; + // //if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') { + // // urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o + // //} + //} + //if (auto mimeData = mimeDataFromTextWithEntities(sel)) { + // updateDragSelection(0, 0, false); + // _widget->noSelectingScroll(); + + // if (!urls.isEmpty()) mimeData->setUrls(urls); + // if (uponSelected && !Adaptive::OneColumn()) { + // auto selectedState = getSelectionState(); + // if (selectedState.count > 0 && selectedState.count == selectedState.canForwardCount) { + // session().data().setMimeForwardIds(getSelectedItems()); + // mimeData->setData(u"application/x-td-forward"_q, "1"); + // } + // } + // _controller->window()->launchDrag(std::move(mimeData)); + // return; + //} else { + // auto forwardMimeType = QString(); + // auto pressedMedia = static_cast(nullptr); + // if (auto pressedItem = Element::Pressed()) { + // pressedMedia = pressedItem->media(); + // if (_mouseCursorState == CursorState::Date) { + // forwardMimeType = u"application/x-td-forward"_q; + // session().data().setMimeForwardIds( + // session().data().itemOrItsGroup(pressedItem->data())); + // } + // } + // if (auto pressedLnkItem = Element::PressedLink()) { + // if ((pressedMedia = pressedLnkItem->media())) { + // if (forwardMimeType.isEmpty() + // && pressedMedia->dragItemByHandler(pressedHandler)) { + // forwardMimeType = u"application/x-td-forward"_q; + // session().data().setMimeForwardIds( + // { 1, pressedLnkItem->fullId() }); + // } + // } + // } + // if (!forwardMimeType.isEmpty()) { + // auto mimeData = std::make_unique(); + // mimeData->setData(forwardMimeType, "1"); + // if (auto document = (pressedMedia ? pressedMedia->getDocument() : nullptr)) { + // auto filepath = document->filepath(true); + // if (!filepath.isEmpty()) { + // QList urls; + // urls.push_back(QUrl::fromLocalFile(filepath)); + // mimeData->setUrls(urls); + // } + // } + + // // This call enters event loop and can destroy any QObject. + // _controller->window()->launchDrag(std::move(mimeData)); + // return; + // } + //} // TODO +} + +int InnerWidget::itemTop(not_null view) const +{ + return _itemsTop + view->y(); +} + +void InnerWidget::repaintItem(const Element *view) +{ + if (!view) { + return; + } + const auto top = itemTop(view); + const auto range = view->verticalRepaintRange(); + update(0, top + range.top, width(), range.height); +} + +void InnerWidget::resizeItem(not_null view) +{ + updateSize(); +} + +void InnerWidget::refreshItem(not_null view) +{ + // No need to refresh views in admin log. +} + +QPoint InnerWidget::mapPointToItem(QPoint point, const Element *view) const +{ + if (!view) { + return QPoint(); + } + return point - QPoint(0, itemTop(view)); +} + +InnerWidget::~InnerWidget() = default; + +} // namespace EditedLog diff --git a/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_inner.h b/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_inner.h new file mode 100644 index 000000000..e3a672268 --- /dev/null +++ b/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_inner.h @@ -0,0 +1,312 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "history/view/history_view_element.h" +#include "ayu/ui/sections/edited/edited_log_item.h" +#include "ayu/ui/sections/edited/edited_log_section.h" +#include "menu/menu_antispam_validator.h" +#include "ui/rp_widget.h" +#include "ui/effects/animations.h" +#include "ui/widgets/tooltip.h" +#include "mtproto/sender.h" +#include "base/timer.h" + +struct ChatRestrictionsInfo; + +namespace Main +{ +class Session; +} // namespace Main + +namespace HistoryView +{ +class Element; +struct TextState; +struct StateRequest; +enum class CursorState : char; +enum class PointState : char; +} // namespace HistoryView + +namespace Ui +{ +class PopupMenu; +class ChatStyle; +struct PeerUserpicView; +} // namespace Ui + +namespace Window +{ +class SessionController; +} // namespace Window + +namespace EditedLog +{ + +class SectionMemento; + +class InnerWidget final + : public Ui::RpWidget, public Ui::AbstractTooltipShower, public HistoryView::ElementDelegate +{ +public: + InnerWidget( + QWidget *parent, + not_null controller, + not_null peer, + not_null item); + + [[nodiscard]] Main::Session &session() const; + [[nodiscard]] not_null theme() const + { + return _theme.get(); + } + + [[nodiscard]] rpl::producer scrollToSignal() const; + + [[nodiscard]] not_null channel() const + { + return _peer; + } + + // Set the correct scroll position after being resized. + void restoreScrollPosition(); + + void resizeToWidth(int newWidth, int minHeight) + { + _minHeight = minHeight; + return TWidget::resizeToWidth(newWidth); + } + + void saveState(not_null memento); + void restoreState(not_null memento); + + // Ui::AbstractTooltipShower interface. + QString tooltipText() const override; + QPoint tooltipPos() const override; + bool tooltipWindowActive() const override; + + // HistoryView::ElementDelegate interface. + HistoryView::Context elementContext() override; + bool elementUnderCursor( + not_null view) override; + [[nodiscard]] float64 elementHighlightOpacity( + not_null item) const override; + bool elementInSelectionMode() override; + bool elementIntersectsRange( + not_null view, + int from, + int till) override; + void elementStartStickerLoop( + not_null view) override; + void elementShowPollResults( + not_null poll, + FullMsgId context) override; + void elementOpenPhoto( + not_null photo, + FullMsgId context) override; + void elementOpenDocument( + not_null document, + FullMsgId context, + bool showInMediaView = false) override; + void elementCancelUpload(const FullMsgId &context) override; + void elementShowTooltip( + const TextWithEntities &text, + Fn hiddenCallback) override; + bool elementAnimationsPaused() override; + bool elementHideReply( + not_null view) override; + bool elementShownUnread( + not_null view) override; + void elementSendBotCommand( + const QString &command, + const FullMsgId &context) override; + void elementHandleViaClick(not_null bot) override; + bool elementIsChatWide() override; + not_null elementPathShiftGradient() override; + void elementReplyTo(const FullMsgId &to) override; + void elementStartInteraction( + not_null view) override; + void elementStartPremium( + not_null view, + HistoryView::Element *replacing) override; + void elementCancelPremium( + not_null view) override; + QString elementAuthorRank( + not_null view) override; + + ~InnerWidget(); + +protected: + void visibleTopBottomUpdated( + int visibleTop, + int visibleBottom) override; + + void paintEvent(QPaintEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseDoubleClickEvent(QMouseEvent *e) override; + void enterEventHook(QEnterEvent *e) override; + void leaveEventHook(QEvent *e) override; + void contextMenuEvent(QContextMenuEvent *e) override; + + // Resizes content and counts natural widget height for the desired width. + int resizeGetHeight(int newWidth) override; + +private: + using Element = HistoryView::Element; + enum class Direction + { + Up, + Down, + }; + enum class MouseAction + { + None, + PrepareDrag, + Dragging, + Selecting, + }; + enum class EnumItemsDirection + { + TopToBottom, + BottomToTop, + }; + using TextState = HistoryView::TextState; + using CursorState = HistoryView::CursorState; + using PointState = HistoryView::PointState; + using StateRequest = HistoryView::StateRequest; + + void mouseActionStart(const QPoint &screenPos, Qt::MouseButton button); + void mouseActionUpdate(const QPoint &screenPos); + void mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button); + void mouseActionCancel(); + void updateSelected(); + void performDrag(); + int itemTop(not_null view) const; + void repaintItem(const Element *view); + void refreshItem(not_null view); + void resizeItem(not_null view); + QPoint mapPointToItem(QPoint point, const Element *view) const; + + void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false); + void savePhotoToFile(not_null photo); + void saveDocumentToFile(not_null document); + void copyContextImage(not_null photo); + void showStickerPackInfo(not_null document); + void cancelContextDownload(not_null document); + void showContextInFolder(not_null document); + void openContextGif(FullMsgId itemId); + void copyContextText(FullMsgId itemId); + void copySelectedText(); + TextForMimeData getSelectedText() const; + + void updateVisibleTopItem(); + void itemsAdded(Direction direction, int addedCount); + void updateSize(); + void updateEmptyText(); + void paintEmpty(Painter &p, not_null st); + void addEvents(Direction direction); + Element *viewForItem(const HistoryItem *item); + + void toggleScrollDateShown(); + void repaintScrollDateCallback(); + bool displayScrollDate() const; + void scrollDateHide(); + void scrollDateCheck(); + void scrollDateHideByTimer(); + + // This function finds all history items that are displayed and calls template method + // for each found message (in given direction) in the passed history with passed top offset. + // + // Method has "bool (*Method)(not_null view, int itemtop, int itembottom)" signature + // if it returns false the enumeration stops immidiately. + template + void enumerateItems(Method method); + + // This function finds all userpics on the left that are displayed and calls template method + // for each found userpic (from the top to the bottom) using enumerateItems() method. + // + // Method has "bool (*Method)(not_null view, int userpicTop)" signature + // if it returns false the enumeration stops immediately. + template + void enumerateUserpics(Method method); + + // This function finds all date elements that are displayed and calls template method + // for each found date element (from the bottom to the top) using enumerateItems() method. + // + // Method has "bool (*Method)(not_null item, int itemtop, int dateTop)" signature + // if it returns false the enumeration stops immediately. + template + void enumerateDates(Method method); + + const not_null _controller; + const not_null _peer; + const not_null _item; + const not_null _history; + MTP::Sender _api; + + const std::unique_ptr _pathGradient; + std::shared_ptr _theme; + + std::vector _items; + std::set _eventIds; + std::map, not_null> _itemsByData; + base::flat_map, TimeId> _itemDates; + base::flat_set _animatedStickersPlayed; + base::flat_map, Ui::PeerUserpicView> _userpics; + base::flat_map, Ui::PeerUserpicView> _userpicsCache; + int _itemsTop = 0; + int _itemsWidth = 0; + int _itemsHeight = 0; + + int _minHeight = 0; + int _visibleTop = 0; + int _visibleBottom = 0; + Element *_visibleTopItem = nullptr; + int _visibleTopFromItem = 0; + + bool _isChatWide = false; + bool _scrollDateShown = false; + Ui::Animations::Simple _scrollDateOpacity; + SingleQueuedInvokation _scrollDateCheck; + base::Timer _scrollDateHideTimer; + Element *_scrollDateLastItem = nullptr; + int _scrollDateLastItemTop = 0; + + // Don't load anything until the memento was read. + bool _upLoaded = true; + bool _downLoaded = true; + bool _filterChanged = false; + Ui::Text::String _emptyText; + + MouseAction _mouseAction = MouseAction::None; + TextSelectType _mouseSelectType = TextSelectType::Letters; + QPoint _dragStartPosition; + QPoint _mousePosition; + Element *_mouseActionItem = nullptr; + CursorState _mouseCursorState = CursorState(); + uint16 _mouseTextSymbol = 0; + bool _pressWasInactive = false; + + Element *_selectedItem = nullptr; + TextSelection _selectedText; + bool _wasSelectedText = false; // was some text selected in current drag action + Qt::CursorShape _cursor = style::cur_default; + + base::unique_qptr _menu; + + QPoint _trippleClickPoint; + base::Timer _trippleClickTimer; + + rpl::event_stream _scrollToSignal; + +}; + +} // namespace EditedLog diff --git a/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_item.cpp b/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_item.cpp new file mode 100644 index 000000000..f45769e72 --- /dev/null +++ b/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_item.cpp @@ -0,0 +1,1952 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include +#include "ayu/ui/sections/edited/edited_log_item.h" + +#include "ayu/ui/sections/edited/edited_log_inner.h" +#include "history/view/history_view_element.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_item_helpers.h" +#include "history/history_location_manager.h" +#include "api/api_chat_participants.h" +#include "api/api_text_entities.h" +#include "data/data_channel.h" +#include "data/data_file_origin.h" +#include "data/data_forum_topic.h" +#include "data/data_user.h" +#include "data/data_session.h" +#include "data/data_message_reaction_id.h" +#include "data/stickers/data_custom_emoji.h" +#include "lang/lang_keys.h" +#include "ui/text/format_values.h" +#include "ui/text/text_utilities.h" +#include "ui/basic_click_handlers.h" +#include "boxes/sticker_set_box.h" +#include "base/unixtime.h" +#include "core/application.h" +#include "core/click_handler_types.h" +#include "main/main_session.h" +#include "window/notifications_manager.h" +#include "window/window_session_controller.h" + +namespace EditedLog +{ +namespace +{ + +TextWithEntities PrepareText( + const QString &value, + const QString &emptyValue) +{ + auto result = TextWithEntities{value}; + if (result.text.isEmpty()) { + result.text = emptyValue; + if (!emptyValue.isEmpty()) { + result.entities.push_back({ + EntityType::Italic, + 0, + int(emptyValue.size())}); + } + } + else { + TextUtilities::ParseEntities( + result, + TextParseLinks + | TextParseMentions + | TextParseHashtags + | TextParseBotCommands); + } + return result; +} + +[[nodiscard]] TimeId ExtractSentDate(const MTPMessage &message) +{ + return message.match([](const MTPDmessageEmpty &) + { + return 0; + }, [](const MTPDmessageService &data) + { + return data.vdate().v; + }, [](const MTPDmessage &data) + { + return data.vdate().v; + }); +} + +[[nodiscard]] MsgId ExtractRealMsgId(const MTPMessage &message) +{ + return MsgId(message.match([](const MTPDmessageEmpty &) + { + return 0; + }, [](const MTPDmessageService &data) + { + return data.vid().v; + }, [](const MTPDmessage &data) + { + return data.vid().v; + })); +} + +MTPMessage PrepareLogMessage(const MTPMessage &message, TimeId newDate) +{ + return message.match([&](const MTPDmessageEmpty &data) + { + return MTP_messageEmpty( + data.vflags(), + data.vid(), + data.vpeer_id() ? *data.vpeer_id() : MTPPeer()); + }, [&](const MTPDmessageService &data) + { + const auto removeFlags = MTPDmessageService::Flag::f_out + | MTPDmessageService::Flag::f_post + | MTPDmessageService::Flag::f_reply_to + | MTPDmessageService::Flag::f_ttl_period; + return MTP_messageService( + MTP_flags(data.vflags().v & ~removeFlags), + data.vid(), + data.vfrom_id() ? *data.vfrom_id() : MTPPeer(), + data.vpeer_id(), + MTPMessageReplyHeader(), + MTP_int(newDate), + data.vaction(), + MTPint()); // ttl_period + }, [&](const MTPDmessage &data) + { + const auto removeFlags = MTPDmessage::Flag::f_out + | MTPDmessage::Flag::f_post + | MTPDmessage::Flag::f_reply_to + | MTPDmessage::Flag::f_replies + | MTPDmessage::Flag::f_edit_date + | MTPDmessage::Flag::f_grouped_id + | MTPDmessage::Flag::f_views + | MTPDmessage::Flag::f_forwards + //| MTPDmessage::Flag::f_reactions + | MTPDmessage::Flag::f_restriction_reason + | MTPDmessage::Flag::f_ttl_period; + return MTP_message( + MTP_flags(data.vflags().v & ~removeFlags), + data.vid(), + data.vfrom_id() ? *data.vfrom_id() : MTPPeer(), + data.vpeer_id(), + data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(), + MTP_long(data.vvia_bot_id().value_or_empty()), + MTPMessageReplyHeader(), + MTP_int(newDate), + data.vmessage(), + data.vmedia() ? *data.vmedia() : MTPMessageMedia(), + data.vreply_markup() ? *data.vreply_markup() : MTPReplyMarkup(), + (data.ventities() + ? *data.ventities() + : MTPVector()), + MTP_int(data.vviews().value_or_empty()), + MTP_int(data.vforwards().value_or_empty()), + MTPMessageReplies(), + MTPint(), // edit_date + MTP_string(), + MTP_long(0), // grouped_id + MTPMessageReactions(), + MTPVector(), + MTPint()); // ttl_period + }); +} + +bool MediaCanHaveCaption(const MTPMessage &message) +{ + if (message.type() != mtpc_message) { + return false; + } + const auto &data = message.c_message(); + const auto media = data.vmedia(); + const auto mediaType = media ? media->type() : mtpc_messageMediaEmpty; + return (mediaType == mtpc_messageMediaDocument) + || (mediaType == mtpc_messageMediaPhoto); +} + +uint64 MediaId(const MTPMessage &message) +{ + if (!MediaCanHaveCaption(message)) { + return 0; + } + const auto &media = message.c_message().vmedia(); + return media + ? v::match( + Data::GetFileReferences(*media).data.begin()->first, + [](const auto &d) + { return d.id; }) + : 0; +} + +TextWithEntities ExtractEditedText( + not_null session, + const MTPMessage &message) +{ + if (message.type() != mtpc_message) { + return TextWithEntities(); + } + const auto &data = message.c_message(); + return { + qs(data.vmessage()), + Api::EntitiesFromMTP(session, data.ventities().value_or_empty()) + }; +} + +const auto CollectChanges = []( + auto &phraseMap, + auto plusFlags, + auto minusFlags) +{ + auto withPrefix = [&phraseMap](auto flags, QChar prefix) + { + auto result = QString(); + for (auto &phrase : phraseMap) { + if (flags & phrase.first) { + result.append('\n' + (prefix + phrase.second(tr::now))); + } + } + return result; + }; + const auto kMinus = QChar(0x2212); + return withPrefix(plusFlags & ~minusFlags, '+') + + withPrefix(minusFlags & ~plusFlags, kMinus); +}; + +TextWithEntities GenerateAdminChangeText( + not_null channel, + const TextWithEntities &user, + ChatAdminRightsInfo newRights, + ChatAdminRightsInfo prevRights) +{ + using Flag = ChatAdminRight; + using Flags = ChatAdminRights; + + auto result = tr::lng_admin_log_promoted( + tr::now, + lt_user, + user, + Ui::Text::WithEntities); + + const auto useInviteLinkPhrase = channel->isMegagroup() + && channel->anyoneCanAddMembers(); + const auto invitePhrase = useInviteLinkPhrase + ? tr::lng_admin_log_admin_invite_link + : tr::lng_admin_log_admin_invite_users; + const auto callPhrase = channel->isBroadcast() + ? tr::lng_admin_log_admin_manage_calls_channel + : tr::lng_admin_log_admin_manage_calls; + static auto phraseMap = std::map>{ + {Flag::ChangeInfo, tr::lng_admin_log_admin_change_info}, + {Flag::PostMessages, tr::lng_admin_log_admin_post_messages}, + {Flag::EditMessages, tr::lng_admin_log_admin_edit_messages}, + {Flag::DeleteMessages, tr::lng_admin_log_admin_delete_messages}, + {Flag::BanUsers, tr::lng_admin_log_admin_ban_users}, + {Flag::InviteByLinkOrAdd, invitePhrase}, + {Flag::ManageTopics, tr::lng_admin_log_admin_manage_topics}, + {Flag::PinMessages, tr::lng_admin_log_admin_pin_messages}, + {Flag::ManageCall, tr::lng_admin_log_admin_manage_calls}, + {Flag::AddAdmins, tr::lng_admin_log_admin_add_admins}, + {Flag::Anonymous, tr::lng_admin_log_admin_remain_anonymous}, + }; + phraseMap[Flag::InviteByLinkOrAdd] = invitePhrase; + phraseMap[Flag::ManageCall] = callPhrase; + + if (!channel->isMegagroup()) { + // Don't display "Ban users" changes in channels. + newRights.flags &= ~Flag::BanUsers; + prevRights.flags &= ~Flag::BanUsers; + } + + const auto changes = CollectChanges( + phraseMap, + newRights.flags, + prevRights.flags); + if (!changes.isEmpty()) { + result.text.append('\n' + changes); + } + + return result; +}; + +QString GeneratePermissionsChangeText( + ChatRestrictionsInfo newRights, + ChatRestrictionsInfo prevRights) +{ + using Flag = ChatRestriction; + using Flags = ChatRestrictions; + + static auto phraseMap = std::map>{ + {Flag::ViewMessages, tr::lng_admin_log_banned_view_messages}, + {Flag::SendOther, tr::lng_admin_log_banned_send_messages}, + {Flag::SendPhotos, tr::lng_admin_log_banned_send_photos}, + {Flag::SendVideos, tr::lng_admin_log_banned_send_videos}, + {Flag::SendMusic, tr::lng_admin_log_banned_send_music}, + {Flag::SendFiles, tr::lng_admin_log_banned_send_files}, + { + Flag::SendVoiceMessages, + tr::lng_admin_log_banned_send_voice_messages}, + { + Flag::SendVideoMessages, + tr::lng_admin_log_banned_send_video_messages}, + {Flag::SendStickers + | Flag::SendGifs + | Flag::SendInline + | Flag::SendGames, tr::lng_admin_log_banned_send_stickers}, + {Flag::EmbedLinks, tr::lng_admin_log_banned_embed_links}, + {Flag::SendPolls, tr::lng_admin_log_banned_send_polls}, + {Flag::ChangeInfo, tr::lng_admin_log_admin_change_info}, + {Flag::AddParticipants, tr::lng_admin_log_admin_invite_users}, + {Flag::CreateTopics, tr::lng_admin_log_admin_create_topics}, + {Flag::PinMessages, tr::lng_admin_log_admin_pin_messages}, + }; + return CollectChanges(phraseMap, prevRights.flags, newRights.flags); +} + +TextWithEntities GeneratePermissionsChangeText( + PeerId participantId, + const TextWithEntities &user, + ChatRestrictionsInfo newRights, + ChatRestrictionsInfo prevRights) +{ + using Flag = ChatRestriction; + + const auto newFlags = newRights.flags; + const auto newUntil = newRights.until; + const auto prevFlags = prevRights.flags; + const auto indefinitely = ChannelData::IsRestrictedForever(newUntil); + if (newFlags & Flag::ViewMessages) { + return tr::lng_admin_log_banned( + tr::now, + lt_user, + user, + Ui::Text::WithEntities); + } + else if (newFlags == 0 + && (prevFlags & Flag::ViewMessages) + && !peerIsUser(participantId)) { + return tr::lng_admin_log_unbanned( + tr::now, + lt_user, + user, + Ui::Text::WithEntities); + } + const auto untilText = indefinitely + ? tr::lng_admin_log_restricted_forever(tr::now) + : tr::lng_admin_log_restricted_until( + tr::now, + lt_date, + langDateTime(base::unixtime::parse(newUntil))); + auto result = tr::lng_admin_log_restricted( + tr::now, + lt_user, + user, + lt_until, + TextWithEntities{untilText}, + Ui::Text::WithEntities); + const auto changes = GeneratePermissionsChangeText(newRights, prevRights); + if (!changes.isEmpty()) { + result.text.append('\n' + changes); + } + return result; +} + +QString PublicJoinLink() +{ + return u"(public_join_link)"_q; +} + +QString ExtractInviteLink(const MTPExportedChatInvite &data) +{ + return data.match([&](const MTPDchatInviteExported &data) + { + return qs(data.vlink()); + }, [&](const MTPDchatInvitePublicJoinRequests &data) + { + return PublicJoinLink(); + }); +} + +QString ExtractInviteLinkLabel(const MTPExportedChatInvite &data) +{ + return data.match([&](const MTPDchatInviteExported &data) + { + return qs(data.vtitle().value_or_empty()); + }, [&](const MTPDchatInvitePublicJoinRequests &data) + { + return PublicJoinLink(); + }); +} + +QString InternalInviteLinkUrl(const MTPExportedChatInvite &data) +{ + const auto base64 = ExtractInviteLink(data).toUtf8().toBase64(); + return "internal:show_invite_link/?link=" + QString::fromLatin1(base64); +} + +QString GenerateInviteLinkText(const MTPExportedChatInvite &data) +{ + const auto label = ExtractInviteLinkLabel(data); + return label.isEmpty() ? ExtractInviteLink(data).replace( + u"https://"_q, + QString() + ).replace( + u"t.me/joinchat/"_q, + QString() + ) : label; +} + +TextWithEntities GenerateInviteLinkLink(const MTPExportedChatInvite &data) +{ + const auto text = GenerateInviteLinkText(data); + return text.endsWith(Ui::kQEllipsis) + ? TextWithEntities{.text = text} + : Ui::Text::Link(text, InternalInviteLinkUrl(data)); +} + +TextWithEntities GenerateInviteLinkChangeText( + const MTPExportedChatInvite &newLink, + const MTPExportedChatInvite &prevLink) +{ + auto link = TextWithEntities{GenerateInviteLinkText(newLink)}; + if (!link.text.endsWith(Ui::kQEllipsis)) { + link.entities.push_back({ + EntityType::CustomUrl, + 0, + int(link.text.size()), + InternalInviteLinkUrl(newLink)}); + } + auto result = tr::lng_admin_log_edited_invite_link( + tr::now, + lt_link, + link, + Ui::Text::WithEntities); + result.text.append('\n'); + + const auto label = [](const MTPExportedChatInvite &link) + { + return link.match([](const MTPDchatInviteExported &data) + { + return qs(data.vtitle().value_or_empty()); + }, [&](const MTPDchatInvitePublicJoinRequests &data) + { + return PublicJoinLink(); + }); + }; + const auto expireDate = [](const MTPExportedChatInvite &link) + { + return link.match([](const MTPDchatInviteExported &data) + { + return data.vexpire_date().value_or_empty(); + }, [&](const MTPDchatInvitePublicJoinRequests &data) + { + return TimeId(); + }); + }; + const auto usageLimit = [](const MTPExportedChatInvite &link) + { + return link.match([](const MTPDchatInviteExported &data) + { + return data.vusage_limit().value_or_empty(); + }, [&](const MTPDchatInvitePublicJoinRequests &data) + { + return 0; + }); + }; + const auto requestApproval = [](const MTPExportedChatInvite &link) + { + return link.match([](const MTPDchatInviteExported &data) + { + return data.is_request_needed(); + }, [&](const MTPDchatInvitePublicJoinRequests &data) + { + return true; + }); + }; + const auto wrapDate = [](TimeId date) + { + return date + ? langDateTime(base::unixtime::parse(date)) + : tr::lng_group_invite_expire_never(tr::now); + }; + const auto wrapUsage = [](int count) + { + return count + ? QString::number(count) + : tr::lng_group_invite_usage_any(tr::now); + }; + const auto wasLabel = label(prevLink); + const auto nowLabel = label(newLink); + const auto wasExpireDate = expireDate(prevLink); + const auto nowExpireDate = expireDate(newLink); + const auto wasUsageLimit = usageLimit(prevLink); + const auto nowUsageLimit = usageLimit(newLink); + const auto wasRequestApproval = requestApproval(prevLink); + const auto nowRequestApproval = requestApproval(newLink); + if (wasLabel != nowLabel) { + result.text.append('\n').append( + tr::lng_admin_log_invite_link_label( + tr::now, + lt_previous, + wasLabel, + lt_limit, + nowLabel)); + } + if (wasExpireDate != nowExpireDate) { + result.text.append('\n').append( + tr::lng_admin_log_invite_link_expire_date( + tr::now, + lt_previous, + wrapDate(wasExpireDate), + lt_limit, + wrapDate(nowExpireDate))); + } + if (wasUsageLimit != nowUsageLimit) { + result.text.append('\n').append( + tr::lng_admin_log_invite_link_usage_limit( + tr::now, + lt_previous, + wrapUsage(wasUsageLimit), + lt_limit, + wrapUsage(nowUsageLimit))); + } + if (wasRequestApproval != nowRequestApproval) { + result.text.append('\n').append( + nowRequestApproval + ? tr::lng_admin_log_invite_link_request_needed(tr::now) + : tr::lng_admin_log_invite_link_request_not_needed(tr::now)); + } + + result.entities.push_front( + EntityInText(EntityType::Italic, 0, result.text.size())); + return result; +}; + +auto GenerateParticipantString( + not_null session, + PeerId participantId) +{ + // User name in "User name (@username)" format with entities. + const auto peer = session->data().peer(participantId); + auto name = TextWithEntities{peer->name()}; + if (const auto user = peer->asUser()) { + const auto data = TextUtilities::MentionNameDataFromFields({ + .selfId = session->userId().bare, + .userId = peerToUser(user->id).bare, + .accessHash = user->accessHash(), + }); + name.entities.push_back({ + EntityType::MentionName, + 0, + int(name.text.size()), + data, + }); + } + const auto username = peer->userName(); + if (username.isEmpty()) { + return name; + } + auto mention = TextWithEntities{'@' + username}; + mention.entities.push_back({ + EntityType::Mention, + 0, + int(mention.text.size())}); + return tr::lng_admin_log_user_with_username( + tr::now, + lt_name, + name, + lt_mention, + mention, + Ui::Text::WithEntities); +} + +auto GenerateParticipantChangeText( + not_null channel, + const Api::ChatParticipant &participant, + std::optional oldParticipant = std::nullopt) +{ + using Type = Api::ChatParticipant::Type; + const auto oldRights = oldParticipant + ? oldParticipant->rights() + : ChatAdminRightsInfo(); + const auto oldRestrictions = oldParticipant + ? oldParticipant->restrictions() + : ChatRestrictionsInfo(); + + const auto generateOther = [&](PeerId participantId) + { + auto user = GenerateParticipantString( + &channel->session(), + participantId); + if (oldParticipant && oldParticipant->type() == Type::Admin) { + return GenerateAdminChangeText( + channel, + user, + ChatAdminRightsInfo(), + oldRights); + } + else if (oldParticipant && oldParticipant->type() == Type::Banned) { + return GeneratePermissionsChangeText( + participantId, + user, + ChatRestrictionsInfo(), + oldRestrictions); + } + else if (oldParticipant + && oldParticipant->type() == Type::Restricted + && (participant.type() == Type::Member + || participant.type() == Type::Left)) { + return GeneratePermissionsChangeText( + participantId, + user, + ChatRestrictionsInfo(), + oldRestrictions); + } + return tr::lng_admin_log_invited( + tr::now, + lt_user, + user, + Ui::Text::WithEntities); + }; + + auto result = [&] + { + const auto &peerId = participant.id(); + switch (participant.type()) { + case Api::ChatParticipant::Type::Creator: { + // No valid string here :( + const auto user = GenerateParticipantString( + &channel->session(), + peerId); + if (peerId == channel->session().userPeerId()) { + return GenerateAdminChangeText( + channel, + user, + participant.rights(), + oldRights); + } + return tr::lng_admin_log_transferred( + tr::now, + lt_user, + user, + Ui::Text::WithEntities); + } + case Api::ChatParticipant::Type::Admin: { + const auto user = GenerateParticipantString( + &channel->session(), + peerId); + return GenerateAdminChangeText( + channel, + user, + participant.rights(), + oldRights); + } + case Api::ChatParticipant::Type::Restricted: + case Api::ChatParticipant::Type::Banned: { + const auto user = GenerateParticipantString( + &channel->session(), + peerId); + return GeneratePermissionsChangeText( + peerId, + user, + participant.restrictions(), + oldRestrictions); + } + case Api::ChatParticipant::Type::Left: + case Api::ChatParticipant::Type::Member: return generateOther(peerId); + }; + Unexpected("Participant type in GenerateParticipantChangeText."); + }(); + + result.entities.push_front( + EntityInText(EntityType::Italic, 0, result.text.size())); + return result; +} + +TextWithEntities GenerateParticipantChangeText( + not_null channel, + const MTPChannelParticipant &participant, + std::optional oldParticipant = std::nullopt) +{ + return GenerateParticipantChangeText( + channel, + Api::ChatParticipant(participant, channel), + oldParticipant + ? std::make_optional(Api::ChatParticipant( + *oldParticipant, + channel)) + : std::nullopt); +} + +TextWithEntities GenerateDefaultBannedRightsChangeText( + not_null channel, + ChatRestrictionsInfo rights, + ChatRestrictionsInfo oldRights) +{ + auto result = TextWithEntities{ + tr::lng_admin_log_changed_default_permissions(tr::now) + }; + const auto changes = GeneratePermissionsChangeText(rights, oldRights); + if (!changes.isEmpty()) { + result.text.append('\n' + changes); + } + result.entities.push_front( + EntityInText(EntityType::Italic, 0, result.text.size())); + return result; +} + +[[nodiscard]] bool IsTopicClosed(const MTPForumTopic &topic) +{ + return topic.match([](const MTPDforumTopic &data) + { + return data.is_closed(); + }, [](const MTPDforumTopicDeleted &) + { + return false; + }); +} + +[[nodiscard]] bool IsTopicHidden(const MTPForumTopic &topic) +{ + return topic.match([](const MTPDforumTopic &data) + { + return data.is_hidden(); + }, [](const MTPDforumTopicDeleted &) + { + return false; + }); +} + +[[nodiscard]] TextWithEntities GenerateTopicLink( + not_null channel, + const MTPForumTopic &topic) +{ + return topic.match([&](const MTPDforumTopic &data) + { + return Ui::Text::Link( + Data::ForumTopicIconWithTitle( + data.vid().v, + data.vicon_emoji_id().value_or_empty(), + qs(data.vtitle())), + u"internal:url:https://t.me/c/%1/%2"_q.arg( + peerToChannel(channel->id).bare).arg( + data.vid().v)); + }, [](const MTPDforumTopicDeleted &) + { + return TextWithEntities{u"Deleted"_q}; + }); +} + +} // namespace + +OwnedItem::OwnedItem(std::nullptr_t) +{ +} + +OwnedItem::OwnedItem( + not_null delegate, + not_null data) + : _data(data), _view(_data->createView(delegate)) +{ +} + +OwnedItem::OwnedItem(OwnedItem &&other) + : _data(base::take(other._data)), _view(base::take(other._view)) +{ +} + +OwnedItem &OwnedItem::operator=(OwnedItem &&other) +{ + _data = base::take(other._data); + _view = base::take(other._view); + return *this; +} + +OwnedItem::~OwnedItem() +{ + clearView(); + if (_data) { + _data->destroy(); + } +} + +void OwnedItem::refreshView( + not_null delegate) +{ + _view = _data->createView(delegate); +} + +void OwnedItem::clearView() +{ + _view = nullptr; +} + +void GenerateItems( + not_null delegate, + not_null history, + EditedMessage message, + Fn callback) +{ + const auto session = &history->session(); + const auto id = message.fakeId; + const auto from = history->owner().user(message.fromId); + const auto date = message.entityCreateDate; + const auto addPart = [&]( + not_null item, + TimeId sentDate = 0, + MsgId realId = MsgId()) + { + return callback(OwnedItem(delegate, item), sentDate, realId); + }; + + const auto fromName = from->name(); + const auto fromLink = from->createOpenLink(); + const auto fromLinkText = Ui::Text::Link(fromName, QString()); + + const auto addSimpleServiceMessage = [&]( + const TextWithEntities &text, + MsgId realId = MsgId(), + PhotoData *photo = nullptr) + { + auto message = PreparedServiceText{text}; + message.links.push_back(fromLink); + addPart( + history->makeMessage( + history->nextNonHistoryEntryId(), + MessageFlag::AdminLogEntry, + date, + std::move(message), + peerToUser(from->id), + photo), + 0, + realId); + }; + + const auto makeSimpleTextMessage = [&](TextWithEntities &&text) + { + const auto bodyFlags = MessageFlag::HasFromId | MessageFlag::Local; + const auto bodyReplyTo = FullReplyTo(); + const auto bodyViaBotId = UserId(); + const auto bodyGroupedId = uint64(); + return history->makeMessage( + history->nextNonHistoryEntryId(), + bodyFlags, + bodyReplyTo, + bodyViaBotId, + date, + peerToUser(from->id), + QString(), + std::move(text), + MTP_messageMediaEmpty(), + HistoryMessageMarkupData(), + bodyGroupedId); + }; + + const auto addSimpleTextMessage = [&](TextWithEntities &&text) + { + addPart(makeSimpleTextMessage(std::move(text))); + }; + + const auto text = QString::fromStdString(message.text); + addSimpleTextMessage(Ui::Text::WithEntities(text)); +// +// const auto createChangeAbout = [&](const LogAbout &action) +// { +// const auto newValue = qs(action.vnew_value()); +// const auto oldValue = qs(action.vprev_value()); +// const auto text = (channel->isMegagroup() +// ? (newValue.isEmpty() +// ? tr::lng_admin_log_removed_description_group +// : tr::lng_admin_log_changed_description_group) +// : (newValue.isEmpty() +// ? tr::lng_admin_log_removed_description_channel +// : tr::lng_admin_log_changed_description_channel) +// )(tr::now, lt_from, fromLinkText, Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// +// const auto body = makeSimpleTextMessage( +// PrepareText(newValue, QString())); +// if (!oldValue.isEmpty()) { +// const auto oldDescription = PrepareText(oldValue, QString()); +// body->addLogEntryOriginal( +// id, +// tr::lng_admin_log_previous_description(tr::now), +// oldDescription); +// } +// addPart(body); +// }; +// +// const auto createChangeUsername = [&](const LogUsername &action) +// { +// const auto newValue = qs(action.vnew_value()); +// const auto oldValue = qs(action.vprev_value()); +// const auto text = (channel->isMegagroup() +// ? (newValue.isEmpty() +// ? tr::lng_admin_log_removed_link_group +// : tr::lng_admin_log_changed_link_group) +// : (newValue.isEmpty() +// ? tr::lng_admin_log_removed_link_channel +// : tr::lng_admin_log_changed_link_channel) +// )(tr::now, lt_from, fromLinkText, Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// +// const auto body = makeSimpleTextMessage(newValue.isEmpty() +// ? TextWithEntities() +// : PrepareText( +// history->session().createInternalLinkFull(newValue), +// QString())); +// if (!oldValue.isEmpty()) { +// const auto oldLink = PrepareText( +// history->session().createInternalLinkFull(oldValue), +// QString()); +// body->addLogEntryOriginal( +// id, +// tr::lng_admin_log_previous_link(tr::now), +// oldLink); +// } +// addPart(body); +// }; +// +// const auto createChangePhoto = [&](const LogPhoto &action) +// { +// action.vnew_photo().match([&](const MTPDphoto &data) +// { +// const auto photo = history->owner().processPhoto(data); +// const auto text = (channel->isMegagroup() +// ? tr::lng_admin_log_changed_photo_group +// : tr::lng_admin_log_changed_photo_channel)( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text, MsgId(), photo); +// }, [&](const MTPDphotoEmpty &data) +// { +// const auto text = (channel->isMegagroup() +// ? tr::lng_admin_log_removed_photo_group +// : tr::lng_admin_log_removed_photo_channel)( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// }); +// }; +// +// const auto createToggleInvites = [&](const LogInvites &action) +// { +// const auto enabled = (action.vnew_value().type() == mtpc_boolTrue); +// const auto text = (enabled +// ? tr::lng_admin_log_invites_enabled +// : tr::lng_admin_log_invites_disabled)( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// }; +// +// const auto createToggleSignatures = [&](const LogSign &action) +// { +// const auto enabled = (action.vnew_value().type() == mtpc_boolTrue); +// const auto text = (enabled +// ? tr::lng_admin_log_signatures_enabled +// : tr::lng_admin_log_signatures_disabled)( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// }; +// +// const auto createUpdatePinned = [&](const LogPin &action) +// { +// action.vmessage().match([&](const MTPDmessage &data) +// { +// const auto pinned = data.is_pinned(); +// const auto realId = ExtractRealMsgId(action.vmessage()); +// const auto text = (pinned +// ? tr::lng_admin_log_pinned_message +// : tr::lng_admin_log_unpinned_message)( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text, realId); +// +// const auto detachExistingItem = false; +// addPart( +// history->createItem( +// history->nextNonHistoryEntryId(), +// PrepareLogMessage(action.vmessage(), date), +// MessageFlag::AdminLogEntry, +// detachExistingItem), +// ExtractSentDate(action.vmessage()), +// realId); +// }, [&](const auto &) +// { +// const auto text = tr::lng_admin_log_unpinned_message( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// }); +// }; +// +// const auto createEditMessage = [&](const LogEdit &action) +// { +// const auto realId = ExtractRealMsgId(action.vnew_message()); +// const auto sentDate = ExtractSentDate(action.vnew_message()); +// const auto newValue = ExtractEditedText( +// session, +// action.vnew_message()); +// auto oldValue = ExtractEditedText( +// session, +// action.vprev_message()); +// +// const auto canHaveCaption = MediaCanHaveCaption( +// action.vnew_message()); +// const auto changedCaption = (newValue != oldValue); +// const auto changedMedia = MediaId(action.vnew_message()) +// != MediaId(action.vprev_message()); +// const auto removedCaption = !oldValue.text.isEmpty() +// && newValue.text.isEmpty(); +// const auto text = (!canHaveCaption +// ? tr::lng_admin_log_edited_message +// : (changedMedia && removedCaption) +// ? tr::lng_admin_log_edited_media_and_removed_caption +// : (changedMedia && changedCaption) +// ? tr::lng_admin_log_edited_media_and_caption +// : changedMedia +// ? tr::lng_admin_log_edited_media +// : removedCaption +// ? tr::lng_admin_log_removed_caption +// : changedCaption +// ? tr::lng_admin_log_edited_caption +// : tr::lng_admin_log_edited_message)( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text, realId); +// +// const auto detachExistingItem = false; +// const auto body = history->createItem( +// history->nextNonHistoryEntryId(), +// PrepareLogMessage(action.vnew_message(), date), +// MessageFlag::AdminLogEntry, +// detachExistingItem); +// if (oldValue.text.isEmpty()) { +// oldValue = PrepareText( +// QString(), +// tr::lng_admin_log_empty_text(tr::now)); +// } +// +// body->addLogEntryOriginal( +// id, +// (canHaveCaption +// ? tr::lng_admin_log_previous_caption +// : tr::lng_admin_log_previous_message)(tr::now), +// oldValue); +// addPart(body, sentDate, realId); +// }; +// +// const auto createDeleteMessage = [&](const LogDelete &action) +// { +// const auto realId = ExtractRealMsgId(action.vmessage()); +// const auto text = tr::lng_admin_log_deleted_message( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text, realId); +// +// const auto detachExistingItem = false; +// addPart( +// history->createItem( +// history->nextNonHistoryEntryId(), +// PrepareLogMessage(action.vmessage(), date), +// MessageFlag::AdminLogEntry, +// detachExistingItem), +// ExtractSentDate(action.vmessage()), +// realId); +// }; +// +// const auto createParticipantJoin = [&](const LogJoin &) +// { +// const auto text = (channel->isMegagroup() +// ? tr::lng_admin_log_participant_joined +// : tr::lng_admin_log_participant_joined_channel)( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// }; +// +// const auto createParticipantLeave = [&](const LogLeave &) +// { +// const auto text = (channel->isMegagroup() +// ? tr::lng_admin_log_participant_left +// : tr::lng_admin_log_participant_left_channel)( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// }; +// +// const auto createParticipantInvite = [&](const LogInvite &action) +// { +// addSimpleTextMessage( +// GenerateParticipantChangeText(channel, action.vparticipant())); +// }; +// +// const auto createParticipantToggleBan = [&](const LogBan &action) +// { +// addSimpleTextMessage( +// GenerateParticipantChangeText( +// channel, +// action.vnew_participant(), +// action.vprev_participant())); +// }; +// +// const auto createParticipantToggleAdmin = [&](const LogPromote &action) +// { +// if ((action.vnew_participant().type() == mtpc_channelParticipantAdmin) +// && (action.vprev_participant().type() +// == mtpc_channelParticipantCreator)) { +// // In case of ownership transfer we show that message in +// // the "User > Creator" part and skip the "Creator > Admin" part. +// return; +// } +// addSimpleTextMessage( +// GenerateParticipantChangeText( +// channel, +// action.vnew_participant(), +// action.vprev_participant())); +// }; +// +// const auto createChangeStickerSet = [&](const LogSticker &action) +// { +// const auto set = action.vnew_stickerset(); +// const auto removed = (set.type() == mtpc_inputStickerSetEmpty); +// if (removed) { +// const auto text = tr::lng_admin_log_removed_stickers_group( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// } +// else { +// const auto text = tr::lng_admin_log_changed_stickers_group( +// tr::now, +// lt_from, +// fromLinkText, +// lt_sticker_set, +// Ui::Text::Link( +// tr::lng_admin_log_changed_stickers_set(tr::now), +// QString()), +// Ui::Text::WithEntities); +// const auto setLink = std::make_shared([=]( +// ClickContext context) +// { +// const auto my = context.other +// .value(); +// if (const auto +// controller = my.sessionWindow.get()) { +// controller->show( +// Box( +// controller->uiShow(), +// Data::FromInputSet(set), +// Data::StickersType::Stickers), +// Ui::LayerOption::CloseOther); +// } +// }); +// auto message = PreparedServiceText{text}; +// message.links.push_back(fromLink); +// message.links.push_back(setLink); +// addPart(history->makeMessage( +// history->nextNonHistoryEntryId(), +// MessageFlag::AdminLogEntry, +// date, +// std::move(message), +// peerToUser(from->id))); +// } +// }; +// +// const auto createTogglePreHistoryHidden = [&]( +// const LogPreHistory &action) +// { +// const auto hidden = (action.vnew_value().type() == mtpc_boolTrue); +// const auto text = (hidden +// ? tr::lng_admin_log_history_made_hidden +// : tr::lng_admin_log_history_made_visible)( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// }; +// +// const auto createDefaultBannedRights = [&]( +// const LogPermissions &action) +// { +// addSimpleTextMessage( +// GenerateDefaultBannedRightsChangeText( +// channel, +// ChatRestrictionsInfo(action.vnew_banned_rights()), +// ChatRestrictionsInfo(action.vprev_banned_rights()))); +// }; +// +// const auto createStopPoll = [&](const LogPoll &action) +// { +// const auto realId = ExtractRealMsgId(action.vmessage()); +// const auto text = tr::lng_admin_log_stopped_poll( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text, realId); +// +// const auto detachExistingItem = false; +// addPart( +// history->createItem( +// history->nextNonHistoryEntryId(), +// PrepareLogMessage(action.vmessage(), date), +// MessageFlag::AdminLogEntry, +// detachExistingItem), +// ExtractSentDate(action.vmessage()), +// realId); +// }; +// +// const auto createChangeLinkedChat = [&](const LogDiscussion &action) +// { +// const auto now = history->owner().channelLoaded( +// action.vnew_value().v); +// if (!now) { +// const auto text = (broadcast +// ? tr::lng_admin_log_removed_linked_chat +// : tr::lng_admin_log_removed_linked_channel)( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// } +// else { +// const auto text = (broadcast +// ? tr::lng_admin_log_changed_linked_chat +// : tr::lng_admin_log_changed_linked_channel)( +// tr::now, +// lt_from, +// fromLinkText, +// lt_chat, +// Ui::Text::Link(now->name(), QString()), +// Ui::Text::WithEntities); +// const auto chatLink = std::make_shared([=] +// { +// if (const auto window = +// now->session().tryResolveWindow()) { +// window->showPeerHistory(now); +// } +// }); +// auto message = PreparedServiceText{text}; +// message.links.push_back(fromLink); +// message.links.push_back(chatLink); +// addPart(history->makeMessage( +// history->nextNonHistoryEntryId(), +// MessageFlag::AdminLogEntry, +// date, +// std::move(message), +// peerToUser(from->id))); +// } +// }; +// +// const auto createChangeLocation = [&](const LogLocation &action) +// { +// action.vnew_value().match([&](const MTPDchannelLocation &data) +// { +// const auto address = qs(data.vaddress()); +// const auto link = data.vgeo_point().match([&]( +// const MTPDgeoPoint &data) +// { +// return Ui::Text::Link( +// address, +// LocationClickHandler::Url(Data::LocationPoint( +// data))); +// }, [&](const MTPDgeoPointEmpty &) +// { +// return TextWithEntities{.text = address}; +// }); +// const auto text = tr::lng_admin_log_changed_location_chat( +// tr::now, +// lt_from, +// fromLinkText, +// lt_address, +// link, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// }, [&](const MTPDchannelLocationEmpty &) +// { +// const auto text = tr::lng_admin_log_removed_location_chat( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// }); +// }; +// +// const auto createToggleSlowMode = [&](const LogSlowMode &action) +// { +// if (const auto seconds = action.vnew_value().v) { +// const auto duration = (seconds >= 60) +// ? tr::lng_minutes(tr::now, lt_count, seconds / 60) +// : tr::lng_seconds(tr::now, lt_count, seconds); +// const auto text = tr::lng_admin_log_changed_slow_mode( +// tr::now, +// lt_from, +// fromLinkText, +// lt_duration, +// {.text = duration}, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// } +// else { +// const auto text = tr::lng_admin_log_removed_slow_mode( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// } +// }; +// +// const auto createStartGroupCall = [&](const LogStartCall &data) +// { +// const auto text = (broadcast +// ? tr::lng_admin_log_started_group_call_channel +// : tr::lng_admin_log_started_group_call)( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// }; +// +// const auto createDiscardGroupCall = [&](const LogDiscardCall &data) +// { +// const auto text = (broadcast +// ? tr::lng_admin_log_discarded_group_call_channel +// : tr::lng_admin_log_discarded_group_call)( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// }; +// +// const auto groupCallParticipantPeer = [&]( +// const MTPGroupCallParticipant &data) +// { +// return data.match([&](const MTPDgroupCallParticipant &data) +// { +// return history->owner().peer(peerFromMTP(data.vpeer())); +// }); +// }; +// +// const auto addServiceMessageWithLink = [&]( +// const TextWithEntities &text, +// const ClickHandlerPtr &link) +// { +// auto message = PreparedServiceText{text}; +// message.links.push_back(fromLink); +// message.links.push_back(link); +// addPart(history->makeMessage( +// history->nextNonHistoryEntryId(), +// MessageFlag::AdminLogEntry, +// date, +// std::move(message), +// peerToUser(from->id))); +// }; +// +// const auto createParticipantMute = [&](const LogMute &data) +// { +// const auto participantPeer = groupCallParticipantPeer( +// data.vparticipant()); +// const auto participantPeerLink = participantPeer->createOpenLink(); +// const auto participantPeerLinkText = Ui::Text::Link( +// participantPeer->name(), +// QString()); +// const auto text = (broadcast +// ? tr::lng_admin_log_muted_participant_channel +// : tr::lng_admin_log_muted_participant)( +// tr::now, +// lt_from, +// fromLinkText, +// lt_user, +// participantPeerLinkText, +// Ui::Text::WithEntities); +// addServiceMessageWithLink(text, participantPeerLink); +// }; +// +// const auto createParticipantUnmute = [&](const LogUnmute &data) +// { +// const auto participantPeer = groupCallParticipantPeer( +// data.vparticipant()); +// const auto participantPeerLink = participantPeer->createOpenLink(); +// const auto participantPeerLinkText = Ui::Text::Link( +// participantPeer->name(), +// QString()); +// const auto text = (broadcast +// ? tr::lng_admin_log_unmuted_participant_channel +// : tr::lng_admin_log_unmuted_participant)( +// tr::now, +// lt_from, +// fromLinkText, +// lt_user, +// participantPeerLinkText, +// Ui::Text::WithEntities); +// addServiceMessageWithLink(text, participantPeerLink); +// }; +// +// const auto createToggleGroupCallSetting = [&]( +// const LogCallSetting &data) +// { +// const auto text = (mtpIsTrue(data.vjoin_muted()) +// ? (broadcast +// ? tr::lng_admin_log_disallowed_unmute_self_channel +// : tr::lng_admin_log_disallowed_unmute_self) +// : (broadcast +// ? tr::lng_admin_log_allowed_unmute_self_channel +// : tr::lng_admin_log_allowed_unmute_self))( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// }; +// +// const auto addInviteLinkServiceMessage = [&]( +// const TextWithEntities &text, +// const MTPExportedChatInvite &data, +// ClickHandlerPtr additional = nullptr) +// { +// auto message = PreparedServiceText{text}; +// message.links.push_back(fromLink); +// if (!ExtractInviteLink(data).endsWith(Ui::kQEllipsis)) { +// message.links.push_back(std::make_shared( +// InternalInviteLinkUrl(data))); +// } +// if (additional) { +// message.links.push_back(std::move(additional)); +// } +// addPart(history->makeMessage( +// history->nextNonHistoryEntryId(), +// MessageFlag::AdminLogEntry, +// date, +// std::move(message), +// peerToUser(from->id), +// nullptr)); +// }; +// +// const auto createParticipantJoinByInvite = [&]( +// const LogJoinByInvite &data) +// { +// const auto text = data.is_via_chatlist() +// ? (channel->isMegagroup() +// ? tr::lng_admin_log_participant_joined_by_filter_link +// : tr::lng_admin_log_participant_joined_by_filter_link_channel) +// : (channel->isMegagroup() +// ? tr::lng_admin_log_participant_joined_by_link +// : tr::lng_admin_log_participant_joined_by_link_channel); +// addInviteLinkServiceMessage( +// text( +// tr::now, +// lt_from, +// fromLinkText, +// lt_link, +// GenerateInviteLinkLink(data.vinvite()), +// Ui::Text::WithEntities), +// data.vinvite()); +// }; +// +// const auto createExportedInviteDelete = [&](const LogInviteDelete &data) +// { +// addInviteLinkServiceMessage( +// tr::lng_admin_log_delete_invite_link( +// tr::now, +// lt_from, +// fromLinkText, +// lt_link, +// GenerateInviteLinkLink(data.vinvite()), +// Ui::Text::WithEntities), +// data.vinvite()); +// }; +// +// const auto createExportedInviteRevoke = [&](const LogInviteRevoke &data) +// { +// addInviteLinkServiceMessage( +// tr::lng_admin_log_revoke_invite_link( +// tr::now, +// lt_from, +// fromLinkText, +// lt_link, +// GenerateInviteLinkLink(data.vinvite()), +// Ui::Text::WithEntities), +// data.vinvite()); +// }; +// +// const auto createExportedInviteEdit = [&](const LogInviteEdit &data) +// { +// addSimpleTextMessage( +// GenerateInviteLinkChangeText( +// data.vnew_invite(), +// data.vprev_invite())); +// }; +// +// const auto createParticipantVolume = [&](const LogVolume &data) +// { +// const auto participantPeer = groupCallParticipantPeer( +// data.vparticipant()); +// const auto participantPeerLink = participantPeer->createOpenLink(); +// const auto participantPeerLinkText = Ui::Text::Link( +// participantPeer->name(), +// QString()); +// const auto volume = data.vparticipant().match([&]( +// const MTPDgroupCallParticipant &data) +// { +// return data.vvolume().value_or(10000); +// }); +// const auto volumeText = QString::number(volume / 100) + '%'; +// auto text = (broadcast +// ? tr::lng_admin_log_participant_volume_channel +// : tr::lng_admin_log_participant_volume)( +// tr::now, +// lt_from, +// fromLinkText, +// lt_user, +// participantPeerLinkText, +// lt_percent, +// {.text = volumeText}, +// Ui::Text::WithEntities); +// addServiceMessageWithLink(text, participantPeerLink); +// }; +// +// const auto createChangeHistoryTTL = [&](const LogTTL &data) +// { +// const auto was = data.vprev_value().v; +// const auto now = data.vnew_value().v; +// const auto wrap = [](int duration) -> TextWithEntities +// { +// const auto text = (duration == 5) +// ? u"5 seconds"_q +// : Ui::FormatTTL(duration); +// return {.text = text}; +// }; +// const auto text = !was +// ? tr::lng_admin_log_messages_ttl_set( +// tr::now, +// lt_from, +// fromLinkText, +// lt_duration, +// wrap(now), +// Ui::Text::WithEntities) +// : !now +// ? tr::lng_admin_log_messages_ttl_removed( +// tr::now, +// lt_from, +// fromLinkText, +// lt_duration, +// wrap(was), +// Ui::Text::WithEntities) +// : tr::lng_admin_log_messages_ttl_changed( +// tr::now, +// lt_from, +// fromLinkText, +// lt_previous, +// wrap(was), +// lt_duration, +// wrap(now), +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// }; +// +// const auto createParticipantJoinByRequest = [&]( +// const LogJoinByRequest &data) +// { +// const auto user = channel->owner().user(UserId(data.vapproved_by())); +// const auto linkText = GenerateInviteLinkLink(data.vinvite()); +// const auto text = (linkText.text == PublicJoinLink()) +// ? (channel->isMegagroup() +// ? tr::lng_admin_log_participant_approved_by_request +// : tr::lng_admin_log_participant_approved_by_request_channel)( +// tr::now, +// lt_from, +// fromLinkText, +// lt_user, +// Ui::Text::Link(user->name(), QString()), +// Ui::Text::WithEntities) +// : (channel->isMegagroup() +// ? tr::lng_admin_log_participant_approved_by_link +// : tr::lng_admin_log_participant_approved_by_link_channel)( +// tr::now, +// lt_from, +// fromLinkText, +// lt_link, +// linkText, +// lt_user, +// Ui::Text::Link(user->name(), QString()), +// Ui::Text::WithEntities); +// addInviteLinkServiceMessage( +// text, +// data.vinvite(), +// user->createOpenLink()); +// }; +// +// const auto createToggleNoForwards = [&](const LogNoForwards &data) +// { +// const auto disabled = (data.vnew_value().type() == mtpc_boolTrue); +// const auto text = (disabled +// ? tr::lng_admin_log_forwards_disabled +// : tr::lng_admin_log_forwards_enabled)( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// }; +// +// const auto createSendMessage = [&](const LogSendMessage &data) +// { +// const auto realId = ExtractRealMsgId(data.vmessage()); +// const auto text = tr::lng_admin_log_sent_message( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text, realId); +// +// const auto detachExistingItem = false; +// addPart( +// history->createItem( +// history->nextNonHistoryEntryId(), +// PrepareLogMessage(data.vmessage(), date), +// MessageFlag::AdminLogEntry, +// detachExistingItem), +// ExtractSentDate(data.vmessage()), +// realId); +// }; +// +// const auto createChangeAvailableReactions = [&]( +// const LogChangeAvailableReactions &data) +// { +// const auto text = data.vnew_value().match([&]( +// const MTPDchatReactionsNone &) +// { +// return tr::lng_admin_log_reactions_disabled( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// }, [&](const MTPDchatReactionsSome &data) +// { +// using namespace Window::Notifications; +// auto list = TextWithEntities(); +// for (const auto &one : data.vreactions().v) { +// if (!list.empty()) { +// list.append(", "); +// } +// list.append(Manager::ComposeReactionEmoji( +// session, +// Data::ReactionFromMTP(one))); +// } +// return tr::lng_admin_log_reactions_updated( +// tr::now, +// lt_from, +// fromLinkText, +// lt_emoji, +// list, +// Ui::Text::WithEntities); +// }, [&](const MTPDchatReactionsAll &data) +// { +// return (data.is_allow_custom() +// ? tr::lng_admin_log_reactions_allowed_all +// : tr::lng_admin_log_reactions_allowed_official)( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// }); +// addSimpleServiceMessage(text); +// }; +// +// const auto createChangeUsernames = [&](const LogChangeUsernames &data) +// { +// const auto newValue = data.vnew_value().v; +// const auto oldValue = data.vprev_value().v; +// +// const auto list = [&](const auto &tlList) +// { +// auto result = TextWithEntities(); +// for (const auto &tlValue : tlList) { +// result.append(PrepareText( +// history->session().createInternalLinkFull(qs(tlValue)), +// QString())); +// result.append('\n'); +// } +// return result; +// }; +// +// if (newValue.size() == oldValue.size()) { +// if (newValue.size() == 1) { +// const auto tl = MTP_channelAdminLogEventActionChangeUsername( +// newValue.front(), +// oldValue.front()); +// tl.match([&](const LogUsername &data) +// { +// createChangeUsername(data); +// }, [](const auto &) +// { +// }); +// return; +// } +// else { +// const auto wasReordered = [&] +// { +// for (const auto &newLink : newValue) { +// if (!ranges::contains(oldValue, newLink)) { +// return false; +// } +// } +// return true; +// }(); +// if (wasReordered) { +// addSimpleServiceMessage((channel->isMegagroup() +// ? tr::lng_admin_log_reordered_link_group +// : tr::lng_admin_log_reordered_link_channel)( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities)); +// const auto body = makeSimpleTextMessage(list(newValue)); +// body->addLogEntryOriginal( +// id, +// tr::lng_admin_log_previous_links_order(tr::now), +// list(oldValue)); +// addPart(body); +// return; +// } +// } +// } +// else if (std::abs(newValue.size() - oldValue.size()) == 1) { +// const auto activated = newValue.size() > oldValue.size(); +// const auto changed = [&] +// { +// const auto value = activated ? oldValue : newValue; +// for (const auto &link : (activated ? newValue : oldValue)) { +// if (!ranges::contains(value, link)) { +// return qs(link); +// } +// } +// return QString(); +// }(); +// addSimpleServiceMessage((activated +// ? tr::lng_admin_log_activated_link +// : tr::lng_admin_log_deactivated_link)( +// tr::now, +// lt_from, +// fromLinkText, +// lt_link, +// {changed}, +// Ui::Text::WithEntities)); +// return; +// } +// // Probably will never happen. +// auto resultText = fromLinkText; +// addSimpleServiceMessage(resultText.append({ +// .text = channel->isMegagroup() +// ? u" changed list of group links:"_q +// : u" changed list of channel links:"_q, +// })); +// const auto body = makeSimpleTextMessage(list(newValue)); +// body->addLogEntryOriginal( +// id, +// "Previous links", +// list(oldValue)); +// addPart(body); +// }; +// +// const auto createToggleForum = [&](const LogToggleForum &data) +// { +// const auto enabled = (data.vnew_value().type() == mtpc_boolTrue); +// const auto text = (enabled +// ? tr::lng_admin_log_topics_enabled +// : tr::lng_admin_log_topics_disabled)( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// }; +// +// const auto createCreateTopic = [&](const LogCreateTopic &data) +// { +// auto topicLink = GenerateTopicLink(channel, data.vtopic()); +// addSimpleServiceMessage(tr::lng_admin_log_topics_created( +// tr::now, +// lt_from, +// fromLinkText, +// lt_topic, +// topicLink, +// Ui::Text::WithEntities)); +// }; +// +// const auto createEditTopic = [&](const LogEditTopic &data) +// { +// const auto prevLink = GenerateTopicLink(channel, data.vprev_topic()); +// const auto nowLink = GenerateTopicLink(channel, data.vnew_topic()); +// if (prevLink != nowLink) { +// addSimpleServiceMessage(tr::lng_admin_log_topics_changed( +// tr::now, +// lt_from, +// fromLinkText, +// lt_topic, +// prevLink, +// lt_new_topic, +// nowLink, +// Ui::Text::WithEntities)); +// } +// const auto wasClosed = IsTopicClosed(data.vprev_topic()); +// const auto nowClosed = IsTopicClosed(data.vnew_topic()); +// if (nowClosed != wasClosed) { +// addSimpleServiceMessage((nowClosed +// ? tr::lng_admin_log_topics_closed +// : tr::lng_admin_log_topics_reopened)( +// tr::now, +// lt_from, +// fromLinkText, +// lt_topic, +// nowLink, +// Ui::Text::WithEntities)); +// } +// const auto wasHidden = IsTopicHidden(data.vprev_topic()); +// const auto nowHidden = IsTopicHidden(data.vnew_topic()); +// if (nowHidden != wasHidden) { +// addSimpleServiceMessage((nowHidden +// ? tr::lng_admin_log_topics_hidden +// : tr::lng_admin_log_topics_unhidden)( +// tr::now, +// lt_from, +// fromLinkText, +// lt_topic, +// nowLink, +// Ui::Text::WithEntities)); +// } +// }; +// +// const auto createDeleteTopic = [&](const LogDeleteTopic &data) +// { +// auto topicLink = GenerateTopicLink(channel, data.vtopic()); +// if (!topicLink.entities.empty()) { +// topicLink.entities.erase(topicLink.entities.begin()); +// } +// addSimpleServiceMessage(tr::lng_admin_log_topics_deleted( +// tr::now, +// lt_from, +// fromLinkText, +// lt_topic, +// topicLink, +// Ui::Text::WithEntities)); +// }; +// +// const auto createPinTopic = [&](const LogPinTopic &data) +// { +// if (const auto &topic = data.vnew_topic()) { +// auto topicLink = GenerateTopicLink(channel, *topic); +// addSimpleServiceMessage(tr::lng_admin_log_topics_pinned( +// tr::now, +// lt_from, +// fromLinkText, +// lt_topic, +// topicLink, +// Ui::Text::WithEntities)); +// } +// else if (const auto &previous = data.vprev_topic()) { +// auto topicLink = GenerateTopicLink(channel, *previous); +// addSimpleServiceMessage(tr::lng_admin_log_topics_unpinned( +// tr::now, +// lt_from, +// fromLinkText, +// lt_topic, +// topicLink, +// Ui::Text::WithEntities)); +// } +// }; +// +// const auto createToggleAntiSpam = [&](const LogToggleAntiSpam &data) +// { +// const auto enabled = (data.vnew_value().type() == mtpc_boolTrue); +// const auto text = (enabled +// ? tr::lng_admin_log_antispam_enabled +// : tr::lng_admin_log_antispam_disabled)( +// tr::now, +// lt_from, +// fromLinkText, +// Ui::Text::WithEntities); +// addSimpleServiceMessage(text); +// }; +// +// action.match( +// createChangeTitle, +// createChangeAbout, +// createChangeUsername, +// createChangePhoto, +// createToggleInvites, +// createToggleSignatures, +// createUpdatePinned, +// createEditMessage, +// createDeleteMessage, +// createParticipantJoin, +// createParticipantLeave, +// createParticipantInvite, +// createParticipantToggleBan, +// createParticipantToggleAdmin, +// createChangeStickerSet, +// createTogglePreHistoryHidden, +// createDefaultBannedRights, +// createStopPoll, +// createChangeLinkedChat, +// createChangeLocation, +// createToggleSlowMode, +// createStartGroupCall, +// createDiscardGroupCall, +// createParticipantMute, +// createParticipantUnmute, +// createToggleGroupCallSetting, +// createParticipantJoinByInvite, +// createExportedInviteDelete, +// createExportedInviteRevoke, +// createExportedInviteEdit, +// createParticipantVolume, +// createChangeHistoryTTL, +// createParticipantJoinByRequest, +// createToggleNoForwards, +// createSendMessage, +// createChangeAvailableReactions, +// createChangeUsernames, +// createToggleForum, +// createCreateTopic, +// createEditTopic, +// createDeleteTopic, +// createPinTopic, +// createToggleAntiSpam); +} + +} // namespace EditedLog diff --git a/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_item.h b/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_item.h new file mode 100644 index 000000000..09677f1c7 --- /dev/null +++ b/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_item.h @@ -0,0 +1,67 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ayu/database/entities.h" + +class History; + +namespace HistoryView +{ +class ElementDelegate; +class Element; +} // namespace HistoryView + +namespace EditedLog +{ + +class OwnedItem; + +void GenerateItems( + not_null delegate, + not_null history, + EditedMessage message, + Fn callback); + +// Smart pointer wrapper for HistoryItem* that destroys the owned item. +class OwnedItem +{ +public: + OwnedItem(std::nullptr_t = nullptr); + OwnedItem( + not_null delegate, + not_null data); + OwnedItem(const OwnedItem &other) = delete; + OwnedItem &operator=(const OwnedItem &other) = delete; + OwnedItem(OwnedItem &&other); + OwnedItem &operator=(OwnedItem &&other); + ~OwnedItem(); + + [[nodiscard]] HistoryView::Element *get() const + { + return _view.get(); + } + [[nodiscard]] HistoryView::Element *operator->() const + { + return get(); + } + [[nodiscard]] operator HistoryView::Element *() const + { + return get(); + } + + void refreshView(not_null delegate); + void clearView(); + +private: + HistoryItem *_data = nullptr; + std::unique_ptr _view; + +}; + +} // namespace EditedLog diff --git a/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_section.cpp b/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_section.cpp new file mode 100644 index 000000000..fcdcb2586 --- /dev/null +++ b/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_section.cpp @@ -0,0 +1,370 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ayu/ui/sections/edited/edited_log_section.h" + +#include "ayu/ui/sections/edited/edited_log_inner.h" +#include "profile/profile_back_button.h" +#include "core/shortcuts.h" +#include "ui/effects/animations.h" +#include "ui/widgets/scroll_area.h" +#include "ui/widgets/shadow.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" +#include "ui/ui_utility.h" +#include "mainwidget.h" +#include "mainwindow.h" +#include "apiwrap.h" +#include "window/themes/window_theme.h" +#include "window/window_adaptive.h" +#include "window/window_session_controller.h" +#include "ui/boxes/confirm_box.h" +#include "base/timer.h" +#include "data/data_channel.h" +#include "data/data_session.h" +#include "lang/lang_keys.h" +#include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" +#include "styles/style_window.h" +#include "styles/style_info.h" + +namespace EditedLog +{ + +class FixedBar final : public TWidget +{ +public: + FixedBar( + QWidget *parent, + not_null controller, + not_null peer); + + // When animating mode is enabled the content is hidden and the + // whole fixed bar acts like a back button. + void setAnimatingMode(bool enabled); + + void goBack(); + +protected: + void mousePressEvent(QMouseEvent *e) override; + void paintEvent(QPaintEvent *e) override; + int resizeGetHeight(int newWidth) override; + +private: + not_null _controller; + not_null _peer; + object_ptr _backButton; + object_ptr _cancel; + + bool _animatingMode = false; +}; + +object_ptr SectionMemento::createWidget( + QWidget *parent, + not_null controller, + Window::Column column, + const QRect &geometry) +{ + if (column == Window::Column::Third) { + return nullptr; + } + auto result = object_ptr(parent, controller, _peer, _item); + result->setInternalState(geometry, this); + return result; +} + +FixedBar::FixedBar( + QWidget *parent, + not_null controller, + not_null peer) + : TWidget(parent), _controller(controller), _peer(peer), _backButton( + this, + &controller->session(), + tr::lng_admin_log_title_all(tr::now), + controller->adaptive().oneColumnValue()), _cancel(this, st::historyAdminLogCancelSearch) +{ + _backButton->moveToLeft(0, 0); + _backButton->setClickedCallback([=] + { goBack(); }); + + _cancel->hide(anim::type::instant); +} + +void FixedBar::goBack() +{ + _controller->showBackFromStack(); +} + +int FixedBar::resizeGetHeight(int newWidth) +{ + auto filterLeft = newWidth; + + auto cancelLeft = filterLeft - _cancel->width(); + _cancel->moveToLeft(cancelLeft, 0); + + auto searchShownLeft = st::topBarArrowPadding.left(); + auto searchHiddenLeft = filterLeft - 0; + auto searchCurrentLeft = anim::interpolate(searchHiddenLeft, searchShownLeft, 0.0); + _backButton->resizeToWidth(searchCurrentLeft); + _backButton->moveToLeft(0, 0); + + auto newHeight = _backButton->height(); + + return newHeight; +} + +void FixedBar::setAnimatingMode(bool enabled) +{ + if (_animatingMode != enabled) { + _animatingMode = enabled; + setCursor(_animatingMode ? style::cur_pointer : style::cur_default); + if (_animatingMode) { + setAttribute(Qt::WA_OpaquePaintEvent, false); + hideChildren(); + } + else { + setAttribute(Qt::WA_OpaquePaintEvent); + showChildren(); + _cancel->setVisible(false); + } + show(); + } +} + +void FixedBar::paintEvent(QPaintEvent *e) +{ + if (!_animatingMode) { + auto p = QPainter(this); + p.fillRect(e->rect(), st::topBarBg); + } +} + +void FixedBar::mousePressEvent(QMouseEvent *e) +{ + if (e->button() == Qt::LeftButton) { + goBack(); + } + else { + TWidget::mousePressEvent(e); + } +} + +Widget::Widget( + QWidget *parent, + not_null controller, + not_null peer, + not_null item) + : Window::SectionWidget(parent, controller, rpl::single(peer)), + _scroll(this, st::historyScroll, false), + _fixedBar(this, controller, peer), + _fixedBarShadow(this), + _whatIsThis( + this, + tr::lng_admin_log_about(tr::now), + st::historyComposeButton), + _item(item) +{ + _fixedBar->move(0, 0); + _fixedBar->resizeToWidth(width()); + _fixedBar->show(); + + _fixedBarShadow->raise(); + + controller->adaptive().value( + ) | rpl::start_with_next([=] + { + updateAdaptiveLayout(); + }, lifetime()); + + _inner = _scroll->setOwnedWidget(object_ptr(this, controller, peer, item)); + _inner->scrollToSignal( + ) | rpl::start_with_next([=](int top) + { + _scroll->scrollToY(top); + }, lifetime()); + + _scroll->move(0, _fixedBar->height()); + _scroll->show(); + _scroll->scrolls( + ) | rpl::start_with_next([=] + { + onScroll(); + }, lifetime()); + + _whatIsThis->setClickedCallback([=] + { + controller->show(Ui::MakeInformBox(peer->isMegagroup() + ? tr::lng_admin_log_about_text() + : tr::lng_admin_log_about_text_channel())); + }); + + setupShortcuts(); +} + +void Widget::updateAdaptiveLayout() +{ + _fixedBarShadow->moveToLeft( + controller()->adaptive().isOneColumn() + ? 0 + : st::lineWidth, + _fixedBar->height()); +} + +not_null Widget::channel() const +{ + return _inner->channel(); +} + +Dialogs::RowDescriptor Widget::activeChat() const +{ + return { + channel()->owner().history(channel()), + FullMsgId(channel()->id, ShowAtUnreadMsgId) + }; +} + +QPixmap Widget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) +{ + if (params.withTopBarShadow) _fixedBarShadow->hide(); + auto result = Ui::GrabWidget(this); + if (params.withTopBarShadow) _fixedBarShadow->show(); + return result; +} + +void Widget::doSetInnerFocus() +{ + _inner->setFocus(); +} + +bool Widget::showInternal( + not_null memento, + const Window::SectionShow ¶ms) +{ + if (auto logMemento = dynamic_cast(memento.get())) { + if (logMemento->getPeer() == channel()) { + restoreState(logMemento); + return true; + } + } + return false; +} + +void Widget::setInternalState(const QRect &geometry, not_null memento) +{ + setGeometry(geometry); + Ui::SendPendingMoveResizeEvents(this); + restoreState(memento); +} + +void Widget::setupShortcuts() +{ + // todo: smth +} + +std::shared_ptr Widget::createMemento() +{ + auto result = std::make_shared(channel(), _item); + saveState(result.get()); + return result; +} + +void Widget::saveState(not_null memento) +{ + memento->setScrollTop(_scroll->scrollTop()); + _inner->saveState(memento); +} + +void Widget::restoreState(not_null memento) +{ + _inner->restoreState(memento); + auto scrollTop = memento->getScrollTop(); + _scroll->scrollToY(scrollTop); + _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); +} + +void Widget::resizeEvent(QResizeEvent *e) +{ + if (!width() || !height()) { + return; + } + + auto contentWidth = width(); + + auto newScrollTop = _scroll->scrollTop() + topDelta(); + _fixedBar->resizeToWidth(contentWidth); + _fixedBarShadow->resize(contentWidth, st::lineWidth); + + auto bottom = height(); + auto scrollHeight = bottom - _fixedBar->height() - _whatIsThis->height(); + auto scrollSize = QSize(contentWidth, scrollHeight); + if (_scroll->size() != scrollSize) { + _scroll->resize(scrollSize); + _inner->resizeToWidth(scrollSize.width(), _scroll->height()); + _inner->restoreScrollPosition(); + } + + if (!_scroll->isHidden()) { + if (topDelta()) { + _scroll->scrollToY(newScrollTop); + } + auto scrollTop = _scroll->scrollTop(); + _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); + } + auto fullWidthButtonRect = myrtlrect(0, bottom - _whatIsThis->height(), contentWidth, _whatIsThis->height()); + _whatIsThis->setGeometry(fullWidthButtonRect); +} + +void Widget::paintEvent(QPaintEvent *e) +{ + if (animatingShow()) { + SectionWidget::paintEvent(e); + return; + } + else if (controller()->contentOverlapped(this, e)) { + return; + } + //if (hasPendingResizedItems()) { + // updateListSize(); + //} + + //auto ms = crl::now(); + //_historyDownShown.step(ms); + + const auto clip = e->rect(); + SectionWidget::PaintBackground(controller(), _inner->theme(), this, clip); +} + +void Widget::onScroll() +{ + int scrollTop = _scroll->scrollTop(); + _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); +} + +void Widget::showAnimatedHook( + const Window::SectionSlideParams ¶ms) +{ + _fixedBar->setAnimatingMode(true); + if (params.withTopBarShadow) _fixedBarShadow->show(); +} + +void Widget::showFinishedHook() +{ + _fixedBar->setAnimatingMode(false); +} + +bool Widget::floatPlayerHandleWheelEvent(QEvent *e) +{ + return _scroll->viewportEvent(e); +} + +QRect Widget::floatPlayerAvailableRect() +{ + return mapToGlobal(_scroll->geometry()); +} + +} // namespace EditedLog + diff --git a/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_section.h b/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_section.h new file mode 100644 index 000000000..df36a5da9 --- /dev/null +++ b/Telegram/SourceFiles/ayu/ui/sections/edited/edited_log_section.h @@ -0,0 +1,159 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "window/section_widget.h" +#include "window/section_memento.h" +#include "ayu/ui/sections/edited/edited_log_item.h" +#include "mtproto/sender.h" + +namespace Ui +{ +class ScrollArea; +class PlainShadow; +class FlatButton; +} // namespace Ui + +namespace Profile +{ +class BackButton; +} // namespace Profile + +namespace EditedLog +{ + +class FixedBar; +class InnerWidget; +class SectionMemento; + +class Widget final : public Window::SectionWidget +{ +public: + Widget( + QWidget *parent, + not_null controller, + not_null peer, + not_null item); + + not_null channel() const; + Dialogs::RowDescriptor activeChat() const override; + + bool hasTopBarShadow() const override + { + return true; + } + + QPixmap grabForShowAnimation(const Window::SectionSlideParams ¶ms) override; + + bool showInternal( + not_null memento, + const Window::SectionShow ¶ms) override; + std::shared_ptr createMemento() override; + + void setInternalState(const QRect &geometry, not_null memento); + + // Float player interface. + bool floatPlayerHandleWheelEvent(QEvent *e) override; + QRect floatPlayerAvailableRect() override; + +protected: + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + + void showAnimatedHook( + const Window::SectionSlideParams ¶ms) override; + void showFinishedHook() override; + void doSetInnerFocus() override; + +private: + void onScroll(); + void updateAdaptiveLayout(); + void saveState(not_null memento); + void restoreState(not_null memento); + void setupShortcuts(); + + object_ptr _scroll; + QPointer _inner; + object_ptr _fixedBar; + object_ptr _fixedBarShadow; + object_ptr _whatIsThis; + not_null _item; + +}; + +class SectionMemento : public Window::SectionMemento +{ +public: + using Element = HistoryView::Element; + + SectionMemento(not_null peer, not_null item) + : _peer(peer), + _item(item) + { + } + + object_ptr createWidget( + QWidget *parent, + not_null controller, + Window::Column column, + const QRect &geometry) override; + + not_null getPeer() const + { + return _peer; + } + void setScrollTop(int scrollTop) + { + _scrollTop = scrollTop; + } + int getScrollTop() const + { + return _scrollTop; + } + + void setItems( + std::vector &&items, + std::set &&eventIds, + bool upLoaded, + bool downLoaded) + { + _items = std::move(items); + _eventIds = std::move(eventIds); + _upLoaded = upLoaded; + _downLoaded = downLoaded; + } + std::vector takeItems() + { + return std::move(_items); + } + std::set takeEventIds() + { + return std::move(_eventIds); + } + bool upLoaded() const + { + return _upLoaded; + } + bool downLoaded() const + { + return _downLoaded; + } + +private: + not_null _peer; + not_null _item; + int _scrollTop = 0; + std::vector> _admins; + std::vector> _adminsCanEdit; + std::vector _items; + std::set _eventIds; + bool _upLoaded = false; + bool _downLoaded = true; +}; + +} // namespace EditedLog diff --git a/Telegram/SourceFiles/ayu/ui/settings/icon_picker.cpp b/Telegram/SourceFiles/ayu/ui/settings/icon_picker.cpp index 9d51897cd..05fb8a60d 100644 --- a/Telegram/SourceFiles/ayu/ui/settings/icon_picker.cpp +++ b/Telegram/SourceFiles/ayu/ui/settings/icon_picker.cpp @@ -4,7 +4,6 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #include "icon_picker.h" #include "ayu/ayu_settings.h" #include "core/application.h" @@ -21,10 +20,9 @@ #include "ayu/utils/windows_utils.h" #endif -void drawIcon(QPainter& p, const QImage& icon, int offset, bool selected) +void drawIcon(QPainter &p, const QImage &icon, int offset, bool selected) { - if (selected) - { + if (selected) { p.save(); p.setPen(QPen(st::iconPreviewStroke, 2)); p.drawEllipse(offset + 2, 2, 68, 68); @@ -35,12 +33,13 @@ void drawIcon(QPainter& p, const QImage& icon, int offset, bool selected) p.drawImage(rect, icon); } -IconPicker::IconPicker(QWidget* parent) : RpWidget(parent) +IconPicker::IconPicker(QWidget *parent) + : RpWidget(parent) { setMinimumSize(st::boxWidth, 72); } -void IconPicker::paintEvent(QPaintEvent* e) +void IconPicker::paintEvent(QPaintEvent *e) { Painter p(this); PainterHighQualityEnabler hq(p); @@ -55,30 +54,26 @@ void IconPicker::paintEvent(QPaintEvent* e) drawIcon(p, icon3, 0 + 64 + 16 + 64 + 16, currentAppLogoName() == AyuSettings::NOTHING_ICON); } -void IconPicker::mousePressEvent(QMouseEvent* e) +void IconPicker::mousePressEvent(QMouseEvent *e) { auto settings = &AyuSettings::getInstance(); auto changed = false; auto x = e->pos().x(); - if (x <= 64 && settings->appIcon != AyuSettings::DEFAULT_ICON) - { + if (x <= 64 && settings->appIcon != AyuSettings::DEFAULT_ICON) { settings->set_appIcon(AyuSettings::DEFAULT_ICON); changed = true; } - else if (x >= 64 + 16 && x <= 64 + 16 + 64 && settings->appIcon != AyuSettings::ALT_ICON) - { + else if (x >= 64 + 16 && x <= 64 + 16 + 64 && settings->appIcon != AyuSettings::ALT_ICON) { settings->set_appIcon(AyuSettings::ALT_ICON); changed = true; } - else if (x >= 64 + 16 + 64 + 16 && x < 64 + 16 + 64 + 16 + 64 && settings->appIcon != AyuSettings::NOTHING_ICON) - { + else if (x >= 64 + 16 + 64 + 16 && x < 64 + 16 + 64 + 16 + 64 && settings->appIcon != AyuSettings::NOTHING_ICON) { settings->set_appIcon(AyuSettings::NOTHING_ICON); changed = true; } - if (changed) - { + if (changed) { AyuSettings::save(); #ifdef Q_OS_WIN diff --git a/Telegram/SourceFiles/ayu/ui/settings/icon_picker.h b/Telegram/SourceFiles/ayu/ui/settings/icon_picker.h index e7a3dd102..46800a839 100644 --- a/Telegram/SourceFiles/ayu/ui/settings/icon_picker.h +++ b/Telegram/SourceFiles/ayu/ui/settings/icon_picker.h @@ -4,7 +4,6 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once #include "ui/rp_widget.h" @@ -12,9 +11,9 @@ class IconPicker : public Ui::RpWidget { public: - IconPicker(QWidget* parent); + IconPicker(QWidget *parent); protected: - void paintEvent(QPaintEvent* e) override; - void mousePressEvent(QMouseEvent* e) override; + void paintEvent(QPaintEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; }; diff --git a/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp b/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp index e485cbf7c..bc6423f25 100644 --- a/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp +++ b/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp @@ -54,29 +54,30 @@ class PainterHighQualityEnabler; const char kStreamerMode[] = "streamer-mode"; -base::options::toggle StreamerMode({ - .id = kStreamerMode, - .name = "Show streamer mode toggles", - .description = "Streamer mode completely hides AyuGram windows and notifications from capture apps.", - .scope = base::options::windows, - .restartRequired = true -}); +base::options::toggle StreamerMode( + { + .id = kStreamerMode, + .name = "Show streamer mode toggles", + .description = "Streamer mode completely hides AyuGram windows and notifications from capture apps.", + .scope = base::options::windows, + .restartRequired = true + } +); -not_null AddInnerToggle( - not_null container, - const style::SettingsButton& st, - std::vector> innerCheckViews, - not_null*> wrap, +not_null AddInnerToggle( + not_null container, + const style::SettingsButton &st, + std::vector> innerCheckViews, + not_null *> wrap, rpl::producer buttonLabel, std::optional locked, - Settings::IconDescriptor&& icon) + Settings::IconDescriptor &&icon) { const auto button = container->add(object_ptr( container, nullptr, st)); - if (icon) - { + if (icon) { AddButtonIcon(button, st, std::move(icon)); } @@ -87,7 +88,7 @@ not_null AddInnerToggle( struct State final { - State(const style::Toggle& st, Fn c) + State(const style::Toggle &st, Fn c) : checkView(st, false, c) { } @@ -95,20 +96,21 @@ not_null AddInnerToggle( Ui::ToggleView checkView; Ui::Animations::Simple animation; rpl::event_stream<> anyChanges; - std::vector> innerChecks; + std::vector> innerChecks; }; const auto state = button->lifetime().make_state( st.toggle, - [=] { toggleButton->update(); }); + [=] + { toggleButton->update(); }); state->innerChecks = std::move(innerCheckViews); const auto countChecked = [=] { return ranges::count_if( state->innerChecks, - [](const auto& v) { return v->checked(); }); + [](const auto &v) + { return v->checked(); }); }; - for (const auto& innerCheck : state->innerChecks) - { + for (const auto &innerCheck : state->innerChecks) { innerCheck->checkedChanges( ) | rpl::to_empty | start_to_stream( state->anyChanges, @@ -119,51 +121,52 @@ not_null AddInnerToggle( const auto separator = Ui::CreateChild(container.get()); separator->paintRequest( ) | start_with_next([=, bg = st.textBgOver] - { - auto p = QPainter(separator); - p.fillRect(separator->rect(), bg); - }, separator->lifetime()); + { + 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()); + ) | 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(toggleButton); checkWidget->resize(checkView->getSize()); checkWidget->paintRequest( ) | start_with_next([=] - { - auto p = QPainter(checkWidget); - checkView->paint(p, 0, 0, checkWidget->width()); - }, checkWidget->lifetime()); + { + 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()); + ) | start_with_next([=](const QSize &s) + { + checkWidget->moveToRight( + st.toggleSkip, + (s.height() - checkWidget->height()) / 2); + }, toggleButton->lifetime()); } state->anyChanges.events_starting_with( rpl::empty_value() ) | rpl::map(countChecked) | start_with_next([=](int count) - { - checkView->setChecked(count == GhostModeOptionsCount, anim::type::normal); - }, toggleButton->lifetime()); + { + checkView->setChecked(count == GhostModeOptionsCount, + anim::type::normal); + }, toggleButton->lifetime()); checkView->setLocked(locked.has_value()); checkView->finishAnimating(); @@ -175,69 +178,68 @@ not_null AddInnerToggle( 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)); - })); + ) | 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)); + })); label->setAttribute(Qt::WA_TransparentForMouseEvents); const auto arrow = Ui::CreateChild(button); { - const auto& icon = st::permissionsExpandIcon; + 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(); - 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()); + { + 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(); + 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(); + ) | 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()); + 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); - }, button->lifetime()); + { + state->animation.start( + [=] + { arrow->update(); }, + toggled ? 0. : 1., + toggled ? 1. : 0., + st::slideWrapDuration); + }, button->lifetime()); const auto handleLocked = [=] { - if (locked.has_value()) - { + if (locked.has_value()) { Ui::Toast::Show(container, *locked); return true; } @@ -246,695 +248,691 @@ not_null AddInnerToggle( button->clicks( ) | start_with_next([=] - { - if (!handleLocked()) - { - wrap->toggle(!wrap->toggled(), anim::type::normal); - } - }, button->lifetime()); + { + if (!handleLocked()) { + wrap->toggle(!wrap->toggled(), anim::type::normal); + } + }, button->lifetime()); toggleButton->clicks( ) | start_with_next([=] - { - if (!handleLocked()) - { - const auto checked = !checkView->checked(); - for (const auto& innerCheck : state->innerChecks) - { - innerCheck->setChecked(checked, anim::type::normal); - } - } - }, toggleButton->lifetime()); + { + if (!handleLocked()) { + const auto checked = !checkView->checked(); + for (const auto &innerCheck : state->innerChecks) { + innerCheck->setChecked(checked, anim::type::normal); + } + } + }, toggleButton->lifetime()); return button; } namespace Settings { - rpl::producer Ayu::title() - { - return tr::ayu_AyuPreferences(); + +rpl::producer Ayu::title() +{ + return tr::ayu_AyuPreferences(); +} + +Ayu::Ayu( + QWidget *parent, + not_null controller) + : Section(parent) +{ + setupContent(controller); +} + +void Ayu::AddPlatformOption( + not_null window, + not_null container, + base::options::option &option, + rpl::producer<> resetClicks) +{ + auto &lifetime = container->lifetime(); + const auto name = option.name().isEmpty() ? option.id() : option.name(); + const auto toggles = lifetime.make_state>(); + std::move( + resetClicks + ) | rpl::map_to( + option.defaultValue() + ) | start_to_stream(*toggles, lifetime); + + const auto button = AddButton( + container, + rpl::single(name), + (option.relevant() + ? st::settingsButtonNoIcon + : st::settingsOptionDisabled) + )->toggleOn(toggles->events_starting_with(option.value())); + + const auto restarter = (option.relevant() && option.restartRequired()) + ? button->lifetime().make_state() + : nullptr; + if (restarter) { + restarter->setCallback([=] + { + window->show(Ui::MakeConfirmBox({ + .text = tr::lng_settings_need_restart(), + .confirmed = [] + { Core::Restart(); }, + .confirmText = tr::lng_settings_restart_now(), + .cancelText = tr::lng_settings_restart_later(), + })); + }); } + button->toggledChanges( + ) | start_with_next([=, &option](bool toggled) + { + if (!option.relevant() && toggled != option.defaultValue()) { + toggles->fire_copy(option.defaultValue()); + window->showToast( + tr::lng_settings_experimental_irrelevant(tr::now)); + return; + } + option.set(toggled); + if (restarter) { + restarter->callOnce(st::settingsButtonNoIcon.toggle.duration); + } + }, container->lifetime()); - Ayu::Ayu( - QWidget* parent, - not_null controller) - : Section(parent) - { - setupContent(controller); + const auto &description = option.description(); + if (!description.isEmpty()) { + AddSkip(container); + AddDividerText(container, rpl::single(description)); } +} - void Ayu::AddPlatformOption( - not_null window, - not_null container, - base::options::option& option, - rpl::producer<> resetClicks) - { - auto& lifetime = container->lifetime(); - const auto name = option.name().isEmpty() ? option.id() : option.name(); - const auto toggles = lifetime.make_state>(); - std::move( - resetClicks - ) | rpl::map_to( - option.defaultValue() - ) | start_to_stream(*toggles, lifetime); +void Ayu::SetupGhostEssentials(not_null container) +{ + auto settings = &AyuSettings::getInstance(); - const auto button = AddButton( + AddSubsectionTitle(container, tr::ayu_GhostEssentialsHeader()); + + const auto widget = object_ptr(this); + + widget->add( + object_ptr( container, - rpl::single(name), - (option.relevant() - ? st::settingsButtonNoIcon - : st::settingsOptionDisabled) - )->toggleOn(toggles->events_starting_with(option.value())); + tr::ayu_GhostEssentialsHeader(), + st::rightsHeaderLabel), + st::rightsHeaderMargin); - const auto restarter = (option.relevant() && option.restartRequired()) - ? button->lifetime().make_state() - : nullptr; - if (restarter) - { - restarter->setCallback([=] - { - window->show(Ui::MakeConfirmBox({ - .text = tr::lng_settings_need_restart(), - .confirmed = [] { Core::Restart(); }, - .confirmText = tr::lng_settings_restart_now(), - .cancelText = tr::lng_settings_restart_later(), - })); - }); - } - button->toggledChanges( - ) | start_with_next([=, &option](bool toggled) - { - if (!option.relevant() && toggled != option.defaultValue()) - { - toggles->fire_copy(option.defaultValue()); - window->showToast( - tr::lng_settings_experimental_irrelevant(tr::now)); - return; - } - option.set(toggled); - if (restarter) - { - restarter->callOnce(st::settingsButtonNoIcon.toggle.duration); - } - }, container->lifetime()); - - const auto& description = option.description(); - if (!description.isEmpty()) - { - AddSkip(container); - AddDividerText(container, rpl::single(description)); - } - } - - void Ayu::SetupGhostEssentials(not_null container) + const auto addCheckbox = [&]( + not_null verticalLayout, + const QString &label, + const bool isCheckedOrig) { - auto settings = &AyuSettings::getInstance(); - - AddSubsectionTitle(container, tr::ayu_GhostEssentialsHeader()); - - const auto widget = object_ptr(this); - - widget->add( - object_ptr( - container, - tr::ayu_GhostEssentialsHeader(), - st::rightsHeaderLabel), - st::rightsHeaderMargin); - - const auto addCheckbox = [&]( - not_null verticalLayout, - const QString& label, - const bool isCheckedOrig) + const auto checkView = [&]() -> not_null { - const auto checkView = [&]() -> not_null - { - const auto checkbox = verticalLayout->add( - object_ptr( - verticalLayout, - label, - isCheckedOrig, - st::settingsCheckbox), - st::rightsButton.padding); - const auto button = Ui::CreateChild( - 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()); + const auto checkbox = verticalLayout->add( + object_ptr( + verticalLayout, + label, + isCheckedOrig, + st::settingsCheckbox), + st::rightsButton.padding); + const auto button = Ui::CreateChild( + 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()); - struct NestedEntry - { - QString checkboxLabel; - bool initial; - std::function callback; - }; + return checkView; + }; - std::vector checkboxes{ - NestedEntry{ - tr::ayu_DontReadMessages(tr::now), !settings->sendReadMessages, [=](bool enabled) - { - settings->set_sendReadMessages(!enabled); - AyuSettings::save(); - } - }, - NestedEntry{ - tr::ayu_DontReadStories(tr::now), !settings->sendReadStories, [=](bool enabled) - { - settings->set_sendReadStories(!enabled); - AyuSettings::save(); - } - }, - NestedEntry{ - tr::ayu_DontSendOnlinePackets(tr::now), !settings->sendOnlinePackets, [=](bool enabled) - { - settings->set_sendOnlinePackets(!enabled); - AyuSettings::save(); - } - }, - NestedEntry{ - tr::ayu_DontSendUploadProgress(tr::now), !settings->sendUploadProgress, [=](bool enabled) - { - settings->set_sendUploadProgress(!enabled); - AyuSettings::save(); - } - }, - NestedEntry{ - tr::ayu_SendOfflinePacketAfterOnline(tr::now), settings->sendOfflinePacketAfterOnline, [=](bool enabled) - { - settings->set_sendOfflinePacketAfterOnline(enabled); - AyuSettings::save(); - } - }, - }; + struct NestedEntry + { + QString checkboxLabel; + bool initial; + std::function callback; + }; - auto wrap = object_ptr>( - container, - object_ptr(container)); - const auto verticalLayout = wrap->entity(); - auto innerChecks = std::vector>(); - for (const auto& entry : checkboxes) - { - const auto c = addCheckbox(verticalLayout, entry.checkboxLabel, entry.initial); - c->checkedValue( - ) | start_with_next([=](bool enabled) + std::vector checkboxes{ + NestedEntry{ + tr::ayu_DontReadMessages(tr::now), !settings->sendReadMessages, [=](bool enabled) { - entry.callback(enabled); - }, container->lifetime()); - innerChecks.push_back(c); - } - - const auto raw = wrap.data(); - raw->hide(anim::type::instant); - AddInnerToggle( - container, - st::rightsButton, - innerChecks, - raw, - tr::ayu_GhostModeToggle(), - std::nullopt, - {}); - container->add(std::move(wrap)); - container->widthValue( - ) | start_with_next([=](int w) - { - raw->resizeToWidth(w); - }, raw->lifetime()); - - AddButton( - container, - tr::ayu_MarkReadAfterSend(), - st::settingsButtonNoIcon - )->toggleOn( - rpl::single(settings->markReadAfterSend) - )->toggledValue( - ) | rpl::filter([=](bool enabled) - { - return (enabled != settings->markReadAfterSend); - }) | start_with_next([=](bool enabled) - { - settings->set_markReadAfterSend(enabled); - AyuSettings::save(); - }, container->lifetime()); - - AddButton( - container, - tr::ayu_UseScheduledMessages(), - st::settingsButtonNoIcon - )->toggleOn( - rpl::single(settings->useScheduledMessages) - )->toggledValue( - ) | rpl::filter([=](bool enabled) - { - return (enabled != settings->useScheduledMessages); - }) | start_with_next([=](bool enabled) - { - settings->set_useScheduledMessages(enabled); - AyuSettings::save(); - }, container->lifetime()); - } - - void Ayu::SetupSpyEssentials(not_null container) - { - auto settings = &AyuSettings::getInstance(); - - AddSubsectionTitle(container, tr::ayu_SpyEssentialsHeader()); - - AddButton( - container, - tr::ayu_SaveDeletedMessages(), - st::settingsButtonNoIcon - )->toggleOn( - rpl::single(settings->saveDeletedMessages) - )->toggledValue( - ) | rpl::filter([=](bool enabled) - { - return (enabled != settings->saveDeletedMessages); - }) | start_with_next([=](bool enabled) - { - settings->set_keepDeletedMessages(enabled); - AyuSettings::save(); - }, container->lifetime()); - - AddButton( - container, - tr::ayu_SaveMessagesHistory(), - st::settingsButtonNoIcon - )->toggleOn( - rpl::single(settings->saveMessagesHistory) - )->toggledValue( - ) | rpl::filter([=](bool enabled) - { - return (enabled != settings->saveMessagesHistory); - }) | start_with_next([=](bool enabled) - { - settings->set_keepMessagesHistory(enabled); - AyuSettings::save(); - }, container->lifetime()); - } - - void Ayu::SetupQoLToggles(not_null container) - { - auto settings = &AyuSettings::getInstance(); - - AddSubsectionTitle(container, tr::ayu_QoLTogglesHeader()); - - AddButton( - container, - tr::ayu_DisableAds(), - st::settingsButtonNoIcon - )->toggleOn( - rpl::single(settings->disableAds) - )->toggledValue( - ) | rpl::filter([=](bool enabled) - { - return (enabled != settings->disableAds); - }) | start_with_next([=](bool enabled) - { - settings->set_disableAds(enabled); - AyuSettings::save(); - }, container->lifetime()); - - AddButton( - container, - tr::ayu_DisableStories(), - st::settingsButtonNoIcon - )->toggleOn( - rpl::single(settings->disableStories) - )->toggledValue( - ) | rpl::filter([=](bool enabled) - { - return (enabled != settings->disableStories); - }) | start_with_next([=](bool enabled) - { - settings->set_disableStories(enabled); - AyuSettings::save(); - }, container->lifetime()); - - AddButton( - container, - tr::ayu_LocalPremium(), - st::settingsButtonNoIcon - )->toggleOn( - rpl::single(settings->localPremium) - )->toggledValue( - ) | rpl::filter([=](bool enabled) - { - return (enabled != settings->localPremium); - }) | start_with_next([=](bool enabled) - { - settings->set_localPremium(enabled); - AyuSettings::save(); - }, container->lifetime()); - - AddButton( - container, - tr::ayu_CopyUsernameAsLink(), - st::settingsButtonNoIcon - )->toggleOn( - rpl::single(settings->copyUsernameAsLink) - )->toggledValue( - ) | rpl::filter([=](bool enabled) - { - return (enabled != settings->copyUsernameAsLink); - }) | start_with_next([=](bool enabled) - { - settings->set_copyUsernameAsLink(enabled); - AyuSettings::save(); - }, container->lifetime()); - } - - void Ayu::SetupAppIcon(not_null container) - { - container->add( - object_ptr(container), - st::settingsSubsectionTitlePadding); - } - - void Ayu::SetupCustomization(not_null container, - not_null controller) - { - auto settings = &AyuSettings::getInstance(); - - AddSubsectionTitle(container, tr::ayu_CustomizationHeader()); - - SetupAppIcon(container); - - auto btn = AddButtonWithLabel( - container, - tr::ayu_DeletedMarkText(), - AyuSettings::get_deletedMarkReactive(), - st::settingsButtonNoIcon - ); - btn->addClickHandler([=]() - { - auto box = Box(); - Ui::show(std::move(box)); - }); - - auto btn2 = AddButtonWithLabel( - container, - tr::ayu_EditedMarkText(), - AyuSettings::get_editedMarkReactive(), - st::settingsButtonNoIcon - ); - btn2->addClickHandler([=]() - { - auto box = Box(); - Ui::show(std::move(box)); - }); - - SetupRecentStickersLimitSlider(container); - - SetupShowPeerId(container, controller); - - AddButton( - container, - tr::ayu_HideAllChats(), - st::settingsButtonNoIcon - )->toggleOn( - rpl::single(settings->hideAllChatsFolder) - )->toggledValue( - ) | rpl::filter([=](bool enabled) - { - return (enabled != settings->hideAllChatsFolder); - }) | start_with_next([=](bool enabled) - { - settings->set_hideAllChatsFolder(enabled); - AyuSettings::save(); - }, container->lifetime()); - - AddButton( - container, - tr::ayu_ShowGhostToggleInDrawer(), - st::settingsButtonNoIcon - )->toggleOn( - rpl::single(settings->showGhostToggleInDrawer) - )->toggledValue( - ) | rpl::filter([=](bool enabled) - { - return (enabled != settings->showGhostToggleInDrawer); - }) | start_with_next([=](bool enabled) - { - settings->set_showGhostToggleInDrawer(enabled); - AyuSettings::save(); - }, container->lifetime()); - - AddButton( - 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) - { - settings->set_showMessageSeconds(enabled); - AyuSettings::save(); - }, container->lifetime()); - } - - void Ayu::SetupShowPeerId(not_null container, - not_null 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 box) - { - const auto save = [=](int index) - { - settings->set_showPeerId(index); - AyuSettings::save(); - }; - SingleChoiceBox(box, { - .title = tr::ayu_SettingsShowID(), - .options = options, - .initialSelection = settings->showPeerId, - .callback = save, - }); - })); - }); - } - - void Ayu::SetupRecentStickersLimitSlider(not_null container) - { - auto settings = &AyuSettings::getInstance(); - - container->add( - CreateButton( - container, - tr::ayu_SettingsRecentStickersCount(), - st::settingsButtonNoIcon) - ); - - auto recentStickersLimitSlider = MakeSliderWithLabel( - container, - st::settingsScale, - st::settingsScaleLabel, - st::normalFont->spacew * 2, - st::settingsScaleLabel.style.font->width("300%")); - container->add( - std::move(recentStickersLimitSlider.widget), - st::settingsScalePadding); - 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( - 100 + 1, // thx tg - [=](int amount) { return amount; }, - settings->recentStickersCount, - [=](int amount) { updateLabel(amount); }, - [=](int amount) - { - updateLabel(amount); - - settings->set_recentStickersCount(amount); + settings->set_sendReadMessages(!enabled); AyuSettings::save(); - }); + } + }, + NestedEntry{ + tr::ayu_DontReadStories(tr::now), !settings->sendReadStories, [=](bool enabled) + { + settings->set_sendReadStories(!enabled); + AyuSettings::save(); + } + }, + NestedEntry{ + tr::ayu_DontSendOnlinePackets(tr::now), !settings->sendOnlinePackets, [=](bool enabled) + { + settings->set_sendOnlinePackets(!enabled); + AyuSettings::save(); + } + }, + NestedEntry{ + tr::ayu_DontSendUploadProgress(tr::now), !settings->sendUploadProgress, [=](bool enabled) + { + settings->set_sendUploadProgress(!enabled); + AyuSettings::save(); + } + }, + NestedEntry{ + tr::ayu_SendOfflinePacketAfterOnline(tr::now), settings->sendOfflinePacketAfterOnline, [=](bool enabled) + { + settings->set_sendOfflinePacketAfterOnline(enabled); + AyuSettings::save(); + } + }, + }; + + auto wrap = object_ptr>( + container, + object_ptr(container)); + const auto verticalLayout = wrap->entity(); + auto innerChecks = std::vector>(); + 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); } - void Ayu::SetupAyuSync(not_null container) - { - AddSubsectionTitle(container, tr::ayu_AyuSyncHeader()); + const auto raw = wrap.data(); + raw->hide(anim::type::instant); + AddInnerToggle( + container, + st::rightsButton, + innerChecks, + raw, + tr::ayu_GhostModeToggle(), + std::nullopt, + {}); + container->add(std::move(wrap)); + container->widthValue( + ) | start_with_next([=](int w) + { + raw->resizeToWidth(w); + }, raw->lifetime()); - auto text = AyuSync::isAgentDownloaded() ? tr::ayu_AyuSyncOpenPreferences() : tr::ayu_AyuSyncDownloadAgent(); + AddButton( + container, + tr::ayu_MarkReadAfterSend(), + st::settingsButtonNoIcon + )->toggleOn( + rpl::single(settings->markReadAfterSend) + )->toggledValue( + ) | rpl::filter([=](bool enabled) + { + return (enabled != settings->markReadAfterSend); + }) | start_with_next([=](bool enabled) + { + settings->set_markReadAfterSend(enabled); + AyuSettings::save(); + }, container->lifetime()); - AddButton( + AddButton( + container, + tr::ayu_UseScheduledMessages(), + st::settingsButtonNoIcon + )->toggleOn( + rpl::single(settings->useScheduledMessages) + )->toggledValue( + ) | rpl::filter([=](bool enabled) + { + return (enabled != settings->useScheduledMessages); + }) | start_with_next([=](bool enabled) + { + settings->set_useScheduledMessages(enabled); + AyuSettings::save(); + }, container->lifetime()); +} + +void Ayu::SetupSpyEssentials(not_null container) +{ + auto settings = &AyuSettings::getInstance(); + + AddSubsectionTitle(container, tr::ayu_SpyEssentialsHeader()); + + AddButton( + container, + tr::ayu_SaveDeletedMessages(), + st::settingsButtonNoIcon + )->toggleOn( + rpl::single(settings->saveDeletedMessages) + )->toggledValue( + ) | rpl::filter([=](bool enabled) + { + return (enabled != settings->saveDeletedMessages); + }) | start_with_next([=](bool enabled) + { + settings->set_keepDeletedMessages(enabled); + AyuSettings::save(); + }, container->lifetime()); + + AddButton( + container, + tr::ayu_SaveMessagesHistory(), + st::settingsButtonNoIcon + )->toggleOn( + rpl::single(settings->saveMessagesHistory) + )->toggledValue( + ) | rpl::filter([=](bool enabled) + { + return (enabled != settings->saveMessagesHistory); + }) | start_with_next([=](bool enabled) + { + settings->set_keepMessagesHistory(enabled); + AyuSettings::save(); + }, container->lifetime()); +} + +void Ayu::SetupQoLToggles(not_null container) +{ + auto settings = &AyuSettings::getInstance(); + + AddSubsectionTitle(container, tr::ayu_QoLTogglesHeader()); + + AddButton( + container, + tr::ayu_DisableAds(), + st::settingsButtonNoIcon + )->toggleOn( + rpl::single(settings->disableAds) + )->toggledValue( + ) | rpl::filter([=](bool enabled) + { + return (enabled != settings->disableAds); + }) | start_with_next([=](bool enabled) + { + settings->set_disableAds(enabled); + AyuSettings::save(); + }, container->lifetime()); + + AddButton( + container, + tr::ayu_DisableStories(), + st::settingsButtonNoIcon + )->toggleOn( + rpl::single(settings->disableStories) + )->toggledValue( + ) | rpl::filter([=](bool enabled) + { + return (enabled != settings->disableStories); + }) | start_with_next([=](bool enabled) + { + settings->set_disableStories(enabled); + AyuSettings::save(); + }, container->lifetime()); + + AddButton( + container, + tr::ayu_LocalPremium(), + st::settingsButtonNoIcon + )->toggleOn( + rpl::single(settings->localPremium) + )->toggledValue( + ) | rpl::filter([=](bool enabled) + { + return (enabled != settings->localPremium); + }) | start_with_next([=](bool enabled) + { + settings->set_localPremium(enabled); + AyuSettings::save(); + }, container->lifetime()); + + AddButton( + container, + tr::ayu_CopyUsernameAsLink(), + st::settingsButtonNoIcon + )->toggleOn( + rpl::single(settings->copyUsernameAsLink) + )->toggledValue( + ) | rpl::filter([=](bool enabled) + { + return (enabled != settings->copyUsernameAsLink); + }) | start_with_next([=](bool enabled) + { + settings->set_copyUsernameAsLink(enabled); + AyuSettings::save(); + }, container->lifetime()); +} + +void Ayu::SetupAppIcon(not_null container) +{ + container->add( + object_ptr(container), + st::settingsSubsectionTitlePadding); +} + +void Ayu::SetupCustomization(not_null container, + not_null controller) +{ + auto settings = &AyuSettings::getInstance(); + + AddSubsectionTitle(container, tr::ayu_CustomizationHeader()); + + SetupAppIcon(container); + + auto btn = AddButtonWithLabel( + container, + tr::ayu_DeletedMarkText(), + AyuSettings::get_deletedMarkReactive(), + st::settingsButtonNoIcon + ); + btn->addClickHandler([=]() + { + auto box = Box(); + Ui::show(std::move(box)); + }); + + auto btn2 = AddButtonWithLabel( + container, + tr::ayu_EditedMarkText(), + AyuSettings::get_editedMarkReactive(), + st::settingsButtonNoIcon + ); + btn2->addClickHandler([=]() + { + auto box = Box(); + Ui::show(std::move(box)); + }); + + SetupRecentStickersLimitSlider(container); + + SetupShowPeerId(container, controller); + + AddButton( + container, + tr::ayu_HideAllChats(), + st::settingsButtonNoIcon + )->toggleOn( + rpl::single(settings->hideAllChatsFolder) + )->toggledValue( + ) | rpl::filter([=](bool enabled) + { + return (enabled != settings->hideAllChatsFolder); + }) | start_with_next([=](bool enabled) + { + settings->set_hideAllChatsFolder(enabled); + AyuSettings::save(); + }, container->lifetime()); + + AddButton( + container, + tr::ayu_ShowGhostToggleInDrawer(), + st::settingsButtonNoIcon + )->toggleOn( + rpl::single(settings->showGhostToggleInDrawer) + )->toggledValue( + ) | rpl::filter([=](bool enabled) + { + return (enabled != settings->showGhostToggleInDrawer); + }) | start_with_next([=](bool enabled) + { + settings->set_showGhostToggleInDrawer(enabled); + AyuSettings::save(); + }, container->lifetime()); + + AddButton( + 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) + { + settings->set_showMessageSeconds(enabled); + AyuSettings::save(); + }, container->lifetime()); +} + +void Ayu::SetupShowPeerId(not_null container, + not_null 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 box) + { + const auto save = [=](int index) + { + settings->set_showPeerId(index); + AyuSettings::save(); + }; + SingleChoiceBox(box, { + .title = tr::ayu_SettingsShowID(), + .options = options, + .initialSelection = settings->showPeerId, + .callback = save, + }); + })); + }); +} + +void Ayu::SetupRecentStickersLimitSlider(not_null container) +{ + auto settings = &AyuSettings::getInstance(); + + container->add( + CreateButton( container, - text, - st::settingsButtonNoIcon - )->addClickHandler([=] + tr::ayu_SettingsRecentStickersCount(), + st::settingsButtonNoIcon) + ); + + auto recentStickersLimitSlider = MakeSliderWithLabel( + container, + st::settingsScale, + st::settingsScaleLabel, + st::normalFont->spacew * 2, + st::settingsScaleLabel.style.font->width("300%")); + container->add( + std::move(recentStickersLimitSlider.widget), + st::settingsScalePadding); + 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( + 100 + 1, // thx tg + [=](int amount) + { return amount; }, + settings->recentStickersCount, + [=](int amount) + { updateLabel(amount); }, + [=](int amount) { - auto controller = &AyuSync::getInstance(); - controller->initializeAgent(); + updateLabel(amount); + + settings->set_recentStickersCount(amount); + AyuSettings::save(); }); - } +} - void Ayu::SetupSendConfirmations(not_null container) - { - auto settings = &AyuSettings::getInstance(); +void Ayu::SetupAyuSync(not_null container) +{ + AddSubsectionTitle(container, tr::ayu_AyuSyncHeader()); - AddSubsectionTitle(container, tr::ayu_ConfirmationsTitle()); + auto text = AyuSync::isAgentDownloaded() ? tr::ayu_AyuSyncOpenPreferences() : tr::ayu_AyuSyncDownloadAgent(); - AddButton( - 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) - { - settings->set_stickerConfirmation(enabled); - AyuSettings::save(); - }, container->lifetime()); + AddButton( + container, + text, + st::settingsButtonNoIcon + )->addClickHandler([=] + { + auto controller = &AyuSync::getInstance(); + controller->initializeAgent(); + }); +} - AddButton( - 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) - { - settings->set_gifConfirmation(enabled); - AyuSettings::save(); - }, container->lifetime()); +void Ayu::SetupSendConfirmations(not_null container) +{ + auto settings = &AyuSettings::getInstance(); - AddButton( - 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) - { - settings->set_voiceConfirmation(enabled); - AyuSettings::save(); - }, container->lifetime()); - } + AddSubsectionTitle(container, tr::ayu_ConfirmationsTitle()); - void Ayu::SetupExperimental(not_null container, - not_null controller) - { - AddSubsectionTitle(container, tr::lng_settings_experimental()); - AddPlatformOption(controller, container, StreamerMode, rpl::producer<>()); - } + AddButton( + 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) + { + settings->set_stickerConfirmation(enabled); + AyuSettings::save(); + }, container->lifetime()); - void Ayu::SetupAyuGramSettings(not_null container, - not_null controller) - { + AddButton( + 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) + { + settings->set_gifConfirmation(enabled); + AyuSettings::save(); + }, container->lifetime()); + + AddButton( + 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) + { + settings->set_voiceConfirmation(enabled); + AyuSettings::save(); + }, container->lifetime()); +} + +void Ayu::SetupExperimental(not_null container, + not_null controller) +{ + AddSubsectionTitle(container, tr::lng_settings_experimental()); + AddPlatformOption(controller, container, StreamerMode, rpl::producer<>()); +} + +void Ayu::SetupAyuGramSettings(not_null container, + not_null controller) +{ + AddSkip(container); + SetupGhostEssentials(container); + AddSkip(container); + + AddDivider(container); + + AddSkip(container); + SetupSpyEssentials(container); + AddSkip(container); + + AddDivider(container); + + AddSkip(container); + SetupQoLToggles(container); + AddSkip(container); + + AddDivider(container); + + AddSkip(container); + SetupCustomization(container, controller); + AddSkip(container); + AddDividerText(container, tr::ayu_SettingsCustomizationHint()); + + // todo: compilation flag + if constexpr (false) { AddSkip(container); - SetupGhostEssentials(container); + SetupAyuSync(container); AddSkip(container); AddDivider(container); - - AddSkip(container); - SetupSpyEssentials(container); - AddSkip(container); - - AddDivider(container); - - AddSkip(container); - SetupQoLToggles(container); - AddSkip(container); - - AddDivider(container); - - AddSkip(container); - SetupCustomization(container, controller); - AddSkip(container); - AddDividerText(container, tr::ayu_SettingsCustomizationHint()); - - // todo: compilation flag - if constexpr (false) - { - AddSkip(container); - SetupAyuSync(container); - AddSkip(container); - - AddDivider(container); - } - - AddSkip(container); - SetupSendConfirmations(container); - AddSkip(container); - - AddDivider(container); - AddSkip(container); - SetupExperimental(container, controller); - - AddDividerText(container, tr::ayu_SettingsWatermark()); } - void Ayu::setupContent(not_null controller) - { - const auto content = Ui::CreateChild(this); + AddSkip(container); + SetupSendConfirmations(container); + AddSkip(container); - SetupAyuGramSettings(content, controller); + AddDivider(container); + AddSkip(container); + SetupExperimental(container, controller); + + AddDividerText(container, tr::ayu_SettingsWatermark()); +} + +void Ayu::setupContent(not_null controller) +{ + const auto content = Ui::CreateChild(this); + + SetupAyuGramSettings(content, controller); + + ResizeFitChild(this, content); +} - ResizeFitChild(this, content); - } } // namespace Settings diff --git a/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.h b/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.h index 37d1c48d0..36dd4217f 100644 --- a/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.h +++ b/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.h @@ -4,7 +4,6 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once #include "base/options.h" @@ -14,54 +13,56 @@ class BoxContent; namespace Window { - class Controller; - - class SessionController; +class Controller; +class SessionController; } // namespace Window extern const char kStreamerMode[]; + extern base::options::toggle StreamerMode; namespace Settings { - class Ayu : public Section - { - public: - Ayu(QWidget* parent, not_null controller); - [[nodiscard]] rpl::producer title() override; +class Ayu : public Section +{ +public: + Ayu(QWidget *parent, not_null controller); - private: - void AddPlatformOption( - not_null window, - not_null container, - base::options::option& option, - rpl::producer<> resetClicks); + [[nodiscard]] rpl::producer title() override; - void SetupGhostEssentials(not_null container); +private: + void AddPlatformOption( + not_null window, + not_null container, + base::options::option &option, + rpl::producer<> resetClicks); - void SetupSpyEssentials(not_null container); + void SetupGhostEssentials(not_null container); - void SetupQoLToggles(not_null container); + void SetupSpyEssentials(not_null container); - void SetupAppIcon(not_null container); + void SetupQoLToggles(not_null container); - void SetupCustomization(not_null container, - not_null controller); + void SetupAppIcon(not_null container); - void SetupShowPeerId(not_null container, not_null controller); + void SetupCustomization(not_null container, + not_null controller); - void SetupRecentStickersLimitSlider(not_null container); + void SetupShowPeerId(not_null container, not_null controller); - void SetupAyuSync(not_null container); + void SetupRecentStickersLimitSlider(not_null container); - void SetupSendConfirmations(not_null container); + void SetupAyuSync(not_null container); - void SetupExperimental(not_null container, - not_null controller); + void SetupSendConfirmations(not_null container); - void SetupAyuGramSettings(not_null container, not_null null); + void SetupExperimental(not_null container, + not_null controller); + + void SetupAyuGramSettings(not_null container, not_null null); + + void setupContent(not_null controller); +}; - void setupContent(not_null controller); - }; } // namespace Settings diff --git a/Telegram/SourceFiles/ayu/ui/utils/ayu_profile_values.cpp b/Telegram/SourceFiles/ayu/ui/utils/ayu_profile_values.cpp index 464b8a985..f9851cee2 100644 --- a/Telegram/SourceFiles/ayu/ui/utils/ayu_profile_values.cpp +++ b/Telegram/SourceFiles/ayu/ui/utils/ayu_profile_values.cpp @@ -13,26 +13,22 @@ constexpr auto kMaxChannelId = -1000000000000; - -QString IDString(not_null peer) +QString IDString(not_null peer) { auto resultId = QString::number(peerIsUser(peer->id) - ? peerToUser(peer->id).bare - : peerIsChat(peer->id) - ? peerToChat(peer->id).bare - : peerIsChannel(peer->id) - ? peerToChannel(peer->id).bare - : peer->id.value); + ? peerToUser(peer->id).bare + : peerIsChat(peer->id) + ? peerToChat(peer->id).bare + : peerIsChannel(peer->id) + ? peerToChannel(peer->id).bare + : peer->id.value); const auto settings = &AyuSettings::getInstance(); - if (settings->showPeerId == 2) - { - if (peer->isChannel()) - { + if (settings->showPeerId == 2) { + if (peer->isChannel()) { resultId = QString::number(peerToChannel(peer->id).bare - kMaxChannelId).prepend("-"); } - else if (peer->isChat()) - { + else if (peer->isChat()) { resultId = resultId.prepend("-"); } } @@ -47,8 +43,7 @@ QString IDString(MsgId topic_root_id) return resultId; } - -rpl::producer IDValue(not_null peer) +rpl::producer IDValue(not_null peer) { return rpl::single(IDString(peer)) | Ui::Text::ToWithEntities(); } diff --git a/Telegram/SourceFiles/ayu/ui/utils/ayu_profile_values.h b/Telegram/SourceFiles/ayu/ui/utils/ayu_profile_values.h index 7d5c4e19a..4bb200a09 100644 --- a/Telegram/SourceFiles/ayu/ui/utils/ayu_profile_values.h +++ b/Telegram/SourceFiles/ayu/ui/utils/ayu_profile_values.h @@ -4,12 +4,11 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once -QString IDString(not_null peer); +QString IDString(not_null peer); QString IDString(MsgId topic_root_id); -rpl::producer IDValue(not_null peer); +rpl::producer IDValue(not_null peer); rpl::producer IDValue(MsgId topicRootId); diff --git a/Telegram/SourceFiles/ayu/utils/ayu_mapper.cpp b/Telegram/SourceFiles/ayu/utils/ayu_mapper.cpp new file mode 100644 index 000000000..8c54aa038 --- /dev/null +++ b/Telegram/SourceFiles/ayu/utils/ayu_mapper.cpp @@ -0,0 +1,86 @@ +// 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, 2023 + +#include "ayu_mapper.h" + +#include "history/history_item.h" +#include "history/history_item_components.h" +#include "history/history.h" + + +namespace AyuMapper +{ + +int mapItemFlagsToMTPFlags(not_null item) { + int flags = 0; + + const auto thread = item->topic() + ? (Data::Thread*)item->topic() + : item->history(); + const auto unseen = item->unread(thread); + if (unseen) { + flags |= 1; + } + + if (item->out()) { + flags |= 2; + } + + if (const auto reply = item->Get()) { + flags |= 4; + } + + if (const auto reply = item->Get()) { + flags |= 8; + } + + if (item->mentionsMe()) { + flags |= 16; + } + + if (item->isUnreadMedia()) { + flags |= 32; + } + + if (item->isSilent()) { + flags |= 8192; + } + + if (item->isPost()) { + flags |= 16384; + } + + if (item->isScheduled()) { + flags |= 262144; + } + + // todo: legacy +// if (item->isLegacy()) { +// flags |= 524288; +// } + + if (item->hideEditedBadge()) { + flags |= 2097152; + } + + if (item->isPinned()) { + flags |= 16777216; + } + + if (item->forbidsForward()) { + flags |= 67108864; + } + + if (item->topic() && item->topicRootId() == item->id) { + flags |= 134217728; + } + + return flags; + +} + +} diff --git a/Telegram/SourceFiles/ayu/utils/ayu_mapper.h b/Telegram/SourceFiles/ayu/utils/ayu_mapper.h new file mode 100644 index 000000000..ee00a9230 --- /dev/null +++ b/Telegram/SourceFiles/ayu/utils/ayu_mapper.h @@ -0,0 +1,14 @@ +// 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, 2023 +#pragma once + +namespace AyuMapper +{ + +int mapItemFlagsToMTPFlags(not_null item); + +} // namespace AyuMapper diff --git a/Telegram/SourceFiles/ayu/utils/telegram_helpers.cpp b/Telegram/SourceFiles/ayu/utils/telegram_helpers.cpp index 515f36f8a..7a36d561e 100644 --- a/Telegram/SourceFiles/ayu/utils/telegram_helpers.cpp +++ b/Telegram/SourceFiles/ayu/utils/telegram_helpers.cpp @@ -20,14 +20,11 @@ #include "history/history_item.h" -Main::Session* getSession(ID userId) +Main::Session *getSession(ID userId) { - for (const auto& [index, account] : Core::App().domain().accounts()) - { - if (const auto session = account->maybeSession()) - { - if (session->userId().bare == userId) - { + for (const auto &[index, account] : Core::App().domain().accounts()) { + if (const auto session = account->maybeSession()) { + if (session->userId().bare == userId) { return session; } } @@ -54,62 +51,56 @@ void dispatchToMainThread(std::function callback) QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0)); } -not_null getHistoryFromDialogId(ID dialogId, Main::Session* session) +not_null getHistoryFromDialogId(ID dialogId, Main::Session *session) { - if (dialogId > 0) - { + if (dialogId > 0) { return session->data().history(peerFromUser(dialogId)); } auto history = session->data().history(peerFromChannel(abs(dialogId))); - if (history->folderKnown()) - { + if (history->folderKnown()) { return history; } return session->data().history(peerFromChat(abs(dialogId))); } -ID getDialogIdFromPeer(not_null peer) +ID getDialogIdFromPeer(not_null peer) { auto peerId = peerIsUser(peer->id) - ? peerToUser(peer->id).bare - : peerIsChat(peer->id) - ? peerToChat(peer->id).bare - : peerIsChannel(peer->id) - ? peerToChannel(peer->id).bare - : peer->id.value; + ? peerToUser(peer->id).bare + : peerIsChat(peer->id) + ? peerToChat(peer->id).bare + : peerIsChannel(peer->id) + ? peerToChannel(peer->id).bare + : peer->id.value; - if (peer->isChannel() || peer->isChat()) - { + if (peer->isChannel() || peer->isChat()) { peerId = -peerId; } return peerId; } -std::pair serializeTextWithEntities(not_null item) +std::pair serializeTextWithEntities(not_null item) { - if (item->emptyText()) - { + if (item->emptyText()) { return std::make_pair("", ""); } auto textWithEntities = item->originalText(); auto text = textWithEntities.text.toStdString(); auto entities = EntitiesToMTP(&item->history()->owner().session(), textWithEntities.entities, - Api::ConvertOption::SkipLocal); + Api::ConvertOption::SkipLocal); - if (entities.v.isEmpty()) - { + if (entities.v.isEmpty()) { return std::make_pair(text, ""); } auto buff = mtpBuffer(); - for (auto entity : entities.v) - { + for (auto entity : entities.v) { entity.write(buff); } - return std::make_pair(text, std::string(reinterpret_cast(buff.data()), buff.size())); + return std::make_pair(text, std::string(reinterpret_cast(buff.data()), buff.size())); } diff --git a/Telegram/SourceFiles/ayu/utils/telegram_helpers.h b/Telegram/SourceFiles/ayu/utils/telegram_helpers.h index 9b18b4f22..b4daf0431 100644 --- a/Telegram/SourceFiles/ayu/utils/telegram_helpers.h +++ b/Telegram/SourceFiles/ayu/utils/telegram_helpers.h @@ -4,7 +4,6 @@ // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 - #pragma once #include "ayu/sync/models.h" @@ -14,9 +13,9 @@ #include "main/main_domain.h" #include "main/main_session.h" -Main::Session* getSession(ID userId); +Main::Session *getSession(ID userId); bool accountExists(ID userId); void dispatchToMainThread(std::function callback); -not_null getHistoryFromDialogId(ID dialogId, Main::Session* session); -ID getDialogIdFromPeer(not_null peer); -std::pair serializeTextWithEntities(not_null item); +not_null getHistoryFromDialogId(ID dialogId, Main::Session *session); +ID getDialogIdFromPeer(not_null peer); +std::pair serializeTextWithEntities(not_null item); diff --git a/Telegram/SourceFiles/ayu/utils/windows_utils.cpp b/Telegram/SourceFiles/ayu/utils/windows_utils.cpp index 9444a50be..651ad23fd 100644 --- a/Telegram/SourceFiles/ayu/utils/windows_utils.cpp +++ b/Telegram/SourceFiles/ayu/utils/windows_utils.cpp @@ -10,7 +10,8 @@ #include -void reloadAppIconFromTaskBar() { +void reloadAppIconFromTaskBar() +{ QString appdata = QDir::fromNativeSeparators(qgetenv("APPDATA")); QString ayugramIconPath = appdata + "/AyuGram.ico"; @@ -24,9 +25,9 @@ void reloadAppIconFromTaskBar() { IPersistFile *pPersistFile = NULL; HRESULT hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, - (void **) &pShellLink); + (void **)&pShellLink); if (SUCCEEDED(hr)) { - hr = pShellLink->QueryInterface(IID_IPersistFile, (void **) &pPersistFile); + hr = pShellLink->QueryInterface(IID_IPersistFile, (void **)&pPersistFile); if (SUCCEEDED(hr)) { WCHAR wszShortcutPath[MAX_PATH]; shortcut.toWCharArray(wszShortcutPath); diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 5bde649a4..1c288ff67 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -80,6 +80,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ayu/database/ayu_database.h" #include "ayu/messages/ayu_messages_controller.h" #include "ayu/ui/boxes/message_history_box.h" +#include "ayu/ui/sections/edited/edited_log_section.h" namespace HistoryView { namespace { @@ -1091,8 +1092,7 @@ base::unique_qptr FillContextMenu( { result->addAction(tr::ayu_EditsHistoryMenuText(tr::now), [=] { - auto box = Box(item); - Ui::show(std::move(box)); + item->history()->session().tryResolveWindow()->showSection(std::make_shared(item->history()->peer, item)); }, &st::menuIconInfo); }