From 7f6221b4094865700a45973fc827abf9ffeaa7fd Mon Sep 17 00:00:00 2001 From: Nikolai Nechaev Date: Thu, 2 Jan 2025 17:49:58 +0300 Subject: [PATCH 001/103] `Manager::startAllHiding`: don't treat fading in notifications specially Previously, `Window::Notifications::Default::Manager` would not start hiding notifications that are fading in when other notifications should hide. This would lead to some notifications never hiding, e.g., when the cursor passes through the notification too quickly and there was not enough time for the notification to fade in completely. Also renamed `Widget::isShowing` -> `Widget::isFadingIn` for clarity. Fixes #28811. --- .../window/notifications_manager_default.cpp | 10 ++-------- .../SourceFiles/window/notifications_manager_default.h | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index 8ff3e2323..5bf6f6099 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -188,16 +188,10 @@ void Manager::checkLastInput() { void Manager::startAllHiding() { if (!hasReplyingNotification()) { - int notHidingCount = 0; for (const auto ¬ification : _notifications) { - if (notification->isShowing()) { - ++notHidingCount; - } else { - notification->startHiding(); - } + notification->startHiding(); } - notHidingCount += _queuedNotifications.size(); - if (_hideAll && notHidingCount < 2) { + if (_hideAll && _queuedNotifications.size() < 2) { _hideAll->startHiding(); } } diff --git a/Telegram/SourceFiles/window/notifications_manager_default.h b/Telegram/SourceFiles/window/notifications_manager_default.h index 5bd4344d8..7c8128235 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.h +++ b/Telegram/SourceFiles/window/notifications_manager_default.h @@ -143,7 +143,7 @@ public: int shift, Direction shiftDirection); - bool isShowing() const { + bool isFadingIn() const { return _a_opacity.animating() && !_hiding; } From 03af444735c8ab72382f0115552e410d8de04ca3 Mon Sep 17 00:00:00 2001 From: Nikolai Nechaev Date: Wed, 1 Jan 2025 11:28:05 +0300 Subject: [PATCH 002/103] Notifications: stop fading in before starting to fade out When a notification is to start hiding (i.e., fade out), it is supposed to start fading out from the maximum opacity, even if it was not fully restored (which only happens if the cursor passed through the notification too quickly). Thus, call `.stop()` for the previous animation, if any, before `.start()`ing the next animation. --- Telegram/SourceFiles/window/notifications_manager_default.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index 5bf6f6099..19d557e72 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -549,6 +549,10 @@ void Widget::hideStop() { void Widget::hideAnimated(float64 duration, const anim::transition &func) { _hiding = true; + // Stop the previous animation so as to make sure that the notification + // is fully restored before hiding it again. + // Relates to https://github.com/telegramdesktop/tdesktop/issues/28811. + _a_opacity.stop(); _a_opacity.start([this] { opacityAnimationCallback(); }, 1., 0., duration, func); } From 4950b5235963f19169291c4403f0ecd376e2a6a6 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 13 Feb 2025 23:54:43 +0400 Subject: [PATCH 003/103] Don't set CMAKE_EXPORT_COMPILE_COMMANDS via cmake.configureSettings It's controlled by cmake.exportCompileCommandsFile and defaults to true --- .devcontainer.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.devcontainer.json b/.devcontainer.json index 30bff840d..366f6ed46 100644 --- a/.devcontainer.json +++ b/.devcontainer.json @@ -9,10 +9,7 @@ "--compile-commands-dir=${workspaceFolder}/out" ], "cmake.generator": "Ninja Multi-Config", - "cmake.buildDirectory": "${workspaceFolder}/out", - "cmake.configureSettings": { - "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" - } + "cmake.buildDirectory": "${workspaceFolder}/out" }, "extensions": [ "ms-vscode.cpptools-extension-pack", From 2ab9587f5f3577f4dc07b2d50c69ca06c0870971 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 18 Feb 2025 11:44:21 +0000 Subject: [PATCH 004/103] Don't wrap QByteArray into std::shared_ptr This has no sense as QByteArray is CoW --- .../platform/linux/notifications_manager_linux.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index d03c83efb..5120200cc 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -511,16 +511,16 @@ void NotificationData::close() { void NotificationData::setImage(QImage image) { if (_notification) { - const auto imageData = std::make_shared(); - QBuffer buffer(imageData.get()); + QByteArray imageData; + QBuffer buffer(&imageData); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); _notification.set_icon( Gio::BytesIcon::new_( GLib::Bytes::new_with_free_func( - reinterpret_cast(imageData->constData()), - imageData->size(), + reinterpret_cast(imageData.constData()), + imageData.size(), [imageData] {}))); return; From cf61dedc79957eb0e5ba8ed5494dd19d9e238977 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 18 Feb 2025 13:24:35 +0000 Subject: [PATCH 005/103] Simplify GNotification actions --- .../platform/linux/integration_linux.cpp | 54 +-------- .../linux/notifications_manager_linux.cpp | 108 +++++++++++++----- .../window/notifications_manager.cpp | 5 - .../window/notifications_manager.h | 32 ------ 4 files changed, 80 insertions(+), 119 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/integration_linux.cpp b/Telegram/SourceFiles/platform/linux/integration_linux.cpp index 147a9f03c..e8696772e 100644 --- a/Telegram/SourceFiles/platform/linux/integration_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/integration_linux.cpp @@ -10,10 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_integration.h" #include "base/platform/base_platform_info.h" #include "base/platform/linux/base_linux_xdp_utilities.h" -#include "window/notifications_manager.h" #include "core/sandbox.h" #include "core/application.h" -#include "core/core_settings.h" #include "base/random.h" #include @@ -27,32 +25,6 @@ namespace { using namespace gi::repository; namespace GObject = gi::repository::GObject; -std::vector AnyVectorFromVariant(GLib::Variant value) { - std::vector result; - - GLib::VariantIter iter; - iter.allocate_(); - iter.init(value); - - const auto uint64Type = GLib::VariantType::new_("t"); - const auto int64Type = GLib::VariantType::new_("x"); - - while (auto value = iter.next_value()) { - value = value.get_variant(); - if (value.is_of_type(uint64Type)) { - result.push_back(std::make_any(value.get_uint64())); - } else if (value.is_of_type(int64Type)) { - result.push_back(std::make_any(value.get_int64())); - } else if (value.is_container()) { - result.push_back( - std::make_any>( - AnyVectorFromVariant(value))); - } - } - - return result; -} - class Application : public Gio::impl::ApplicationImpl { public: Application(); @@ -125,42 +97,18 @@ Application::Application() }); actionMap.add_action(quitAction); - using Window::Notifications::Manager; - using NotificationId = Manager::NotificationId; - - const auto notificationIdVariantType = GLib::VariantType::new_("av"); + const auto notificationIdVariantType = GLib::VariantType::new_("a{sv}"); auto notificationActivateAction = Gio::SimpleAction::new_( "notification-activate", notificationIdVariantType); - notificationActivateAction.signal_activate().connect([]( - Gio::SimpleAction, - GLib::Variant parameter) { - Core::Sandbox::Instance().customEnterFromEventLoop([&] { - Core::App().notifications().manager().notificationActivated( - NotificationId::FromAnyVector( - AnyVectorFromVariant(parameter))); - }); - }); - actionMap.add_action(notificationActivateAction); auto notificationMarkAsReadAction = Gio::SimpleAction::new_( "notification-mark-as-read", notificationIdVariantType); - notificationMarkAsReadAction.signal_activate().connect([]( - Gio::SimpleAction, - GLib::Variant parameter) { - Core::Sandbox::Instance().customEnterFromEventLoop([&] { - Core::App().notifications().manager().notificationReplied( - NotificationId::FromAnyVector( - AnyVectorFromVariant(parameter)), - {}); - }); - }); - actionMap.add_action(notificationMarkAsReadAction); } diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 5120200cc..4a8ac2b12 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -139,32 +139,6 @@ bool UseGNotification() { return KSandbox::isFlatpak() && !ServiceRegistered; } -GLib::Variant AnyVectorToVariant(const std::vector &value) { - return GLib::Variant::new_array( - value | ranges::views::transform([](const std::any &value) { - try { - return GLib::Variant::new_variant( - GLib::Variant::new_uint64(std::any_cast(value))); - } catch (...) { - } - - try { - return GLib::Variant::new_variant( - GLib::Variant::new_int64(std::any_cast(value))); - } catch (...) { - } - - try { - return GLib::Variant::new_variant( - AnyVectorToVariant( - std::any_cast>(value))); - } catch (...) { - } - - return GLib::Variant(nullptr); - }) | ranges::to_vector); -} - class NotificationData final : public base::has_weak_ptr { public: using NotificationId = Window::Notifications::Manager::NotificationId; @@ -212,6 +186,8 @@ private: ulong _notificationRepliedSignalId = 0; ulong _notificationClosedSignalId = 0; + rpl::lifetime _lifetime; + }; using Notification = std::unique_ptr; @@ -238,6 +214,57 @@ bool NotificationData::init(const Info &info) { const auto &subtitle = info.subtitle; if (_application) { + auto actionMap = Gio::ActionMap(_application); + + const auto dictToNotificationId = [](GLib::VariantDict dict) { + using ContextId = Window::Notifications::Manager::ContextId; + return NotificationId{ + .contextId = ContextId{ + .sessionId = dict.lookup_value("session").get_uint64(), + .peerId = PeerId(dict.lookup_value("peer").get_uint64()), + .topicRootId = dict.lookup_value("topic").get_int64(), + }, + .msgId = dict.lookup_value("msgid").get_int64(), + }; + }; + + auto activate = gi::wrap( + G_SIMPLE_ACTION( + actionMap.lookup_action("notification-activate").gobj_()), + gi::transfer_none); + + const auto activateSig = activate.signal_activate().connect([=]( + Gio::SimpleAction, + GLib::Variant parameter) { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + _manager->notificationActivated( + dictToNotificationId(GLib::VariantDict::new_(parameter))); + }); + }); + + _lifetime.add([=]() mutable { + activate.disconnect(activateSig); + }); + + auto markAsRead = gi::wrap( + G_SIMPLE_ACTION( + actionMap.lookup_action("notification-mark-as-read").gobj_()), + gi::transfer_none); + + const auto markAsReadSig = markAsRead.signal_activate().connect([=]( + Gio::SimpleAction, + GLib::Variant parameter) { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + _manager->notificationReplied( + dictToNotificationId(GLib::VariantDict::new_(parameter)), + {}); + }); + }); + + _lifetime.add([=]() mutable { + markAsRead.disconnect(markAsReadSig); + }); + _notification = Gio::Notification::new_( subtitle.isEmpty() ? title.toStdString() @@ -264,17 +291,40 @@ bool NotificationData::init(const Info &info) { set_category(_notification.gobj_(), "im.received"); } - const auto idVariant = AnyVectorToVariant(_id.toAnyVector()); + const auto peer = info.peer; + + const auto notificationVariant = GLib::Variant::new_array({ + GLib::Variant::new_dict_entry( + GLib::Variant::new_string("session"), + GLib::Variant::new_variant( + GLib::Variant::new_uint64(peer->session().uniqueId()))), + GLib::Variant::new_dict_entry( + GLib::Variant::new_string("peer"), + GLib::Variant::new_variant( + GLib::Variant::new_uint64(peer->id.value))), + GLib::Variant::new_dict_entry( + GLib::Variant::new_string("peer"), + GLib::Variant::new_variant( + GLib::Variant::new_uint64(peer->id.value))), + GLib::Variant::new_dict_entry( + GLib::Variant::new_string("topic"), + GLib::Variant::new_variant( + GLib::Variant::new_int64(info.topicRootId.bare))), + GLib::Variant::new_dict_entry( + GLib::Variant::new_string("msgid"), + GLib::Variant::new_variant( + GLib::Variant::new_int64(info.itemId.bare))), + }); _notification.set_default_action_and_target( "app.notification-activate", - idVariant); + notificationVariant); if (!info.options.hideMarkAsRead) { _notification.add_button_with_target( tr::lng_context_mark_read(tr::now).toStdString(), "app.notification-mark-as-read", - idVariant); + notificationVariant); } return true; diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index 3e8f8812e..ce39e6fb1 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -212,11 +212,6 @@ void System::setManager(Fn()> create) { } } -Manager &System::manager() const { - Expects(_manager != nullptr); - return *_manager; -} - Main::Session *System::findSession(uint64 sessionId) const { for (const auto &[index, account] : Core::App().domain().accounts()) { if (const auto session = account->maybeSession()) { diff --git a/Telegram/SourceFiles/window/notifications_manager.h b/Telegram/SourceFiles/window/notifications_manager.h index fa88f6c0a..833a79d00 100644 --- a/Telegram/SourceFiles/window/notifications_manager.h +++ b/Telegram/SourceFiles/window/notifications_manager.h @@ -100,7 +100,6 @@ public: void createManager(); void setManager(Fn()> create); - [[nodiscard]] Manager &manager() const; void checkDelayed(); void schedule(Data::ItemNotification notification); @@ -237,22 +236,6 @@ public: friend inline auto operator<=>( const ContextId&, const ContextId&) = default; - - [[nodiscard]] auto toAnyVector() const { - return std::vector{ - std::make_any(sessionId), - std::make_any(peerId.value), - std::make_any(topicRootId.bare), - }; - } - - [[nodiscard]] static auto FromAnyVector(const auto &vector) { - return ContextId{ - std::any_cast(vector[0]), - PeerIdHelper(std::any_cast(vector[1])), - std::any_cast(vector[2]), - }; - } }; struct NotificationId { ContextId contextId; @@ -261,21 +244,6 @@ public: friend inline auto operator<=>( const NotificationId&, const NotificationId&) = default; - - [[nodiscard]] auto toAnyVector() const { - return std::vector{ - std::make_any>(contextId.toAnyVector()), - std::make_any(msgId.bare), - }; - } - - [[nodiscard]] static auto FromAnyVector(const auto &vector) { - return NotificationId{ - ContextId::FromAnyVector( - std::any_cast>(vector[0])), - std::any_cast(vector[1]), - }; - } }; struct NotificationFields { not_null item; From f810d7c82a9eb7f43851c9874f075625a8e718de Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 18 Feb 2025 21:19:03 +0400 Subject: [PATCH 006/103] Fix spaces on end of lines --- Telegram/SourceFiles/core/changelogs.cpp | 2 +- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/core/changelogs.cpp b/Telegram/SourceFiles/core/changelogs.cpp index 2d0d52fe0..ddb5b8a93 100644 --- a/Telegram/SourceFiles/core/changelogs.cpp +++ b/Telegram/SourceFiles/core/changelogs.cpp @@ -37,7 +37,7 @@ std::map BetaLogs() { "- Nice looking code blocks with syntax highlight.\n" "- Copy full code block by click on its header.\n" - + "- Send a highlighted code block using ```language syntax.\n" } }; diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index c460d1e1c..8eece7d4d 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -261,7 +261,7 @@ bool GenerateDesktopFile( -1, GLib::KeyFileFlags::KEEP_COMMENTS_ | GLib::KeyFileFlags::KEEP_TRANSLATIONS_); - + if (!loaded) { if (!silent) { LOG(("App Error: %1").arg(loaded.error().message_().c_str())); From a6315bef0551b183acc0541c9b3d6f235c29140c Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 18 Feb 2025 20:14:40 +0000 Subject: [PATCH 007/103] Move GNotiftcation action handlers to Manager --- .../linux/notifications_manager_linux.cpp | 104 +++++++++--------- 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 4a8ac2b12..9ee2dcb21 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -186,8 +186,6 @@ private: ulong _notificationRepliedSignalId = 0; ulong _notificationClosedSignalId = 0; - rpl::lifetime _lifetime; - }; using Notification = std::unique_ptr; @@ -214,57 +212,6 @@ bool NotificationData::init(const Info &info) { const auto &subtitle = info.subtitle; if (_application) { - auto actionMap = Gio::ActionMap(_application); - - const auto dictToNotificationId = [](GLib::VariantDict dict) { - using ContextId = Window::Notifications::Manager::ContextId; - return NotificationId{ - .contextId = ContextId{ - .sessionId = dict.lookup_value("session").get_uint64(), - .peerId = PeerId(dict.lookup_value("peer").get_uint64()), - .topicRootId = dict.lookup_value("topic").get_int64(), - }, - .msgId = dict.lookup_value("msgid").get_int64(), - }; - }; - - auto activate = gi::wrap( - G_SIMPLE_ACTION( - actionMap.lookup_action("notification-activate").gobj_()), - gi::transfer_none); - - const auto activateSig = activate.signal_activate().connect([=]( - Gio::SimpleAction, - GLib::Variant parameter) { - Core::Sandbox::Instance().customEnterFromEventLoop([&] { - _manager->notificationActivated( - dictToNotificationId(GLib::VariantDict::new_(parameter))); - }); - }); - - _lifetime.add([=]() mutable { - activate.disconnect(activateSig); - }); - - auto markAsRead = gi::wrap( - G_SIMPLE_ACTION( - actionMap.lookup_action("notification-mark-as-read").gobj_()), - gi::transfer_none); - - const auto markAsReadSig = markAsRead.signal_activate().connect([=]( - Gio::SimpleAction, - GLib::Variant parameter) { - Core::Sandbox::Instance().customEnterFromEventLoop([&] { - _manager->notificationReplied( - dictToNotificationId(GLib::VariantDict::new_(parameter)), - {}); - }); - }); - - _lifetime.add([=]() mutable { - markAsRead.disconnect(markAsReadSig); - }); - _notification = Gio::Notification::new_( subtitle.isEmpty() ? title.toStdString() @@ -632,6 +579,7 @@ private: XdgNotifications::NotificationsProxy _proxy; XdgNotifications::Notifications _interface; + rpl::lifetime _lifetime; }; @@ -810,6 +758,56 @@ Manager::Private::Private(not_null manager) return a + (a.empty() ? "" : ", ") + b; }).c_str())); } + + if (auto actionMap = Gio::ActionMap(Gio::Application::get_default())) { + const auto dictToNotificationId = [](GLib::VariantDict dict) { + return NotificationId{ + .contextId = ContextId{ + .sessionId = dict.lookup_value("session").get_uint64(), + .peerId = PeerId(dict.lookup_value("peer").get_uint64()), + .topicRootId = dict.lookup_value("topic").get_int64(), + }, + .msgId = dict.lookup_value("msgid").get_int64(), + }; + }; + + auto activate = gi::wrap( + G_SIMPLE_ACTION( + actionMap.lookup_action("notification-activate").gobj_()), + gi::transfer_none); + + const auto activateSig = activate.signal_activate().connect([=]( + Gio::SimpleAction, + GLib::Variant parameter) { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + _manager->notificationActivated( + dictToNotificationId(GLib::VariantDict::new_(parameter))); + }); + }); + + _lifetime.add([=]() mutable { + activate.disconnect(activateSig); + }); + + auto markAsRead = gi::wrap( + G_SIMPLE_ACTION( + actionMap.lookup_action("notification-mark-as-read").gobj_()), + gi::transfer_none); + + const auto markAsReadSig = markAsRead.signal_activate().connect([=]( + Gio::SimpleAction, + GLib::Variant parameter) { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + _manager->notificationReplied( + dictToNotificationId(GLib::VariantDict::new_(parameter)), + {}); + }); + }); + + _lifetime.add([=]() mutable { + markAsRead.disconnect(markAsReadSig); + }); + } } void Manager::Private::init(XdgNotifications::NotificationsProxy proxy) { From f64f008f77c6954e386a16d1a2e86bfe1344b2ad Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 20 Feb 2025 01:32:23 +0400 Subject: [PATCH 008/103] Remove fmt from snap It's not really needed for a long time --- snap/snapcraft.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 668c2ea8c..1f12c9d7e 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -94,7 +94,6 @@ parts: - libasound2-dev - libavif-dev - libboost-regex-dev - - libfmt-dev - libgirepository1.0-dev - libglib2.0-dev - libheif-dev From 140ba653b9d032e8d9ba33127837e981d0cbbe77 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 20 Feb 2025 02:50:00 +0400 Subject: [PATCH 009/103] More clean up in libjxl snap part --- snap/snapcraft.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 1f12c9d7e..2474e983a 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -277,15 +277,15 @@ parts: - -DJPEGXL_ENABLE_SKCMS=OFF stage: - -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libbrotli* + - -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libhwy* prime: - -./usr/bin - -./usr/include + - -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/cmake - -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/pkgconfig - -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/*.a - -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/*.so - -./usr/share - after: - - patches qt: plugin: nil From 0c2d00c7926609cb57ca9340a41fd693d4986003 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 20 Feb 2025 06:30:33 +0400 Subject: [PATCH 010/103] More clean up in qt snap part --- snap/snapcraft.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 2474e983a..da8dbc658 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -408,8 +408,10 @@ parts: - -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/*.prl - -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/*.so - -./usr/libexec + - -./usr/metatypes - -./usr/mkspecs - -./usr/modules + - -./**/objects-* after: - libjxl - patches From bd28ac6e1f3d3282d9edf8dbe8ec6f6397817032 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 20 Feb 2025 04:07:44 +0000 Subject: [PATCH 011/103] Re-enable ffmpeg optimizations on Linux They were disabled due to -Ofast but Dockerfile is switched to -O3 since a long time --- Telegram/build/docker/centos_env/Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 48c5f3ccd..16991cbef 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -489,8 +489,6 @@ RUN git clone -b n6.1.1 --depth=1 {{ GIT }}/FFmpeg/FFmpeg.git \ --extra-cflags="-DCONFIG_SAFE_BITSTREAM_READER=1" \ --extra-cxxflags="-DCONFIG_SAFE_BITSTREAM_READER=1" \ --disable-debug \ - --disable-optimizations \ - --disable-inline-asm \ --disable-programs \ --disable-doc \ --disable-network \ From 3b0bd9d1d1d21efd1cb9204a17656ce3fdcd8cd3 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 20 Feb 2025 16:25:05 +0400 Subject: [PATCH 012/103] Remove duplicate entry in qt snap part --- snap/snapcraft.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index da8dbc658..290fbb136 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -401,7 +401,6 @@ parts: - -./usr/doc - -./usr/include - -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/cmake - - -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/metatypes - -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/pkgconfig - -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/*.a - -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/*.la From 0d7175058be3e9def9d427bf022c13f9940f5533 Mon Sep 17 00:00:00 2001 From: jovaska Date: Sun, 23 Feb 2025 20:50:20 +0200 Subject: [PATCH 013/103] Fix compilation with ffmpeg-4.x --- Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp | 5 +++-- .../media/audio/media_audio_local_cache.cpp | 10 ++++++++-- .../SourceFiles/ui/controls/round_video_recorder.cpp | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp index 8fd0f3e97..3811cbc86 100644 --- a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp +++ b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp @@ -546,9 +546,10 @@ SwresamplePointer MakeSwresamplePointer( } // Initialize audio resampler + AvErrorWrap error; #if DA_FFMPEG_NEW_CHANNEL_LAYOUT auto result = (SwrContext*)nullptr; - auto error = AvErrorWrap(swr_alloc_set_opts2( + error = AvErrorWrap(swr_alloc_set_opts2( &result, dstLayout, dstFormat, @@ -564,7 +565,7 @@ SwresamplePointer MakeSwresamplePointer( } #else // DA_FFMPEG_NEW_CHANNEL_LAYOUT auto result = swr_alloc_set_opts( - existing ? existing.get() : nullptr, + existing ? existing->get() : nullptr, dstLayout, dstFormat, dstRate, diff --git a/Telegram/SourceFiles/media/audio/media_audio_local_cache.cpp b/Telegram/SourceFiles/media/audio/media_audio_local_cache.cpp index c8b90f798..7d9f99400 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_local_cache.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio_local_cache.cpp @@ -44,7 +44,13 @@ constexpr auto kFrameSize = 4096; return {}; } + +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 79, 100) auto inCodec = (const AVCodec*)nullptr; +#else + auto inCodec = (AVCodec*)nullptr; +#endif + const auto streamId = av_find_best_stream( input.get(), AVMEDIA_TYPE_AUDIO, @@ -152,10 +158,10 @@ constexpr auto kFrameSize = 4096; inCodecContext->sample_rate, &outCodecContext->ch_layout, #else // DA_FFMPEG_NEW_CHANNEL_LAYOUT - &inCodecContext->channel_layout, + inCodecContext->channel_layout, inCodecContext->sample_fmt, inCodecContext->sample_rate, - &outCodecContext->channel_layout, + outCodecContext->channel_layout, #endif // DA_FFMPEG_NEW_CHANNEL_LAYOUT outCodecContext->sample_fmt, outCodecContext->sample_rate); diff --git a/Telegram/SourceFiles/ui/controls/round_video_recorder.cpp b/Telegram/SourceFiles/ui/controls/round_video_recorder.cpp index 812a4844c..3c904cd6b 100644 --- a/Telegram/SourceFiles/ui/controls/round_video_recorder.cpp +++ b/Telegram/SourceFiles/ui/controls/round_video_recorder.cpp @@ -341,10 +341,10 @@ bool RoundVideoRecorder::Private::initAudio() { &_swrContext); #else // DA_FFMPEG_NEW_CHANNEL_LAYOUT _swrContext = MakeSwresamplePointer( - &_audioCodec->channel_layout, + _audioCodec->channel_layout, AV_SAMPLE_FMT_S16, _audioCodec->sample_rate, - &_audioCodec->channel_layout, + _audioCodec->channel_layout, _audioCodec->sample_fmt, _audioCodec->sample_rate, &_swrContext); From e60d501e4ae3fbd932dc69bb17bb014e49551f24 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 23 Feb 2025 06:49:49 +0400 Subject: [PATCH 014/103] Have a state struct in Linux tray --- .../SourceFiles/platform/linux/tray_linux.cpp | 205 ++++++------------ 1 file changed, 67 insertions(+), 138 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/tray_linux.cpp b/Telegram/SourceFiles/platform/linux/tray_linux.cpp index 2d0e23aec..3680f3751 100644 --- a/Telegram/SourceFiles/platform/linux/tray_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/tray_linux.cpp @@ -44,66 +44,49 @@ public: explicit IconGraphic(); ~IconGraphic(); - [[nodiscard]] bool isRefreshNeeded( - const QIcon &systemIcon, - const QString &iconThemeName, - int counter, - bool muted) const; - [[nodiscard]] QIcon systemIcon( - const QString &iconThemeName, - bool monochrome, - int counter, - bool muted) const; - [[nodiscard]] QIcon trayIcon( - const QIcon &systemIcon, - const QString &iconThemeName, - bool monochrome, - int counter, - bool muted); + void updateState(); + [[nodiscard]] bool isRefreshNeeded() const; + [[nodiscard]] QIcon trayIcon(); private: + struct State { + QIcon systemIcon; + QString iconThemeName; + bool monochrome = false; + int32 counter = 0; + bool muted = false; + }; + + [[nodiscard]] QIcon systemIcon() const; [[nodiscard]] int counterSlice(int counter) const; - void updateIconRegenerationNeeded( - const QIcon &icon, - const QIcon &systemIcon, - const QString &iconThemeName, - bool monochrome, - int counter, - bool muted); [[nodiscard]] QSize dprSize(const QImage &image) const; const int _iconSizes[7]; - bool _muted = true; - int32 _count = 0; base::flat_map _imageBack; QIcon _trayIcon; - QIcon _systemIcon; - QString _themeName; - bool _monochrome; + State _current; + State _new; }; IconGraphic::IconGraphic() : _iconSizes{ 16, 22, 32, 48, 64, 128, 256 } { + updateState(); } IconGraphic::~IconGraphic() = default; -QIcon IconGraphic::systemIcon( - const QString &iconThemeName, - bool monochrome, - int counter, - bool muted) const { - if (iconThemeName == _themeName - && monochrome == _monochrome - && (counter > 0) == (_count > 0) - && muted == _muted) { - return _systemIcon; +QIcon IconGraphic::systemIcon() const { + if (_new.iconThemeName == _current.iconThemeName + && _new.monochrome == _current.monochrome + && (_new.counter > 0) == (_current.counter > 0) + && _new.muted == _current.muted) { + return _current.systemIcon; } const auto candidates = { - monochrome ? PanelIconName(counter, muted) : QString(), + _new.monochrome ? PanelIconName(_new.counter, _new.muted) : QString(), base::IconName(), }; @@ -120,80 +103,63 @@ QIcon IconGraphic::systemIcon( return QIcon(); } - int IconGraphic::counterSlice(int counter) const { return (counter >= 1000) ? (1000 + (counter % 100)) : counter; } -bool IconGraphic::isRefreshNeeded( - const QIcon &systemIcon, - const QString &iconThemeName, - int counter, - bool muted) const { - return _trayIcon.isNull() - || iconThemeName != _themeName - || systemIcon.name() != _systemIcon.name() - || (systemIcon.name() != PanelIconName(counter, muted) - ? muted != _muted || counterSlice(counter) != _count - : false); -} - -void IconGraphic::updateIconRegenerationNeeded( - const QIcon &icon, - const QIcon &systemIcon, - const QString &iconThemeName, - bool monochrome, - int counter, - bool muted) { - _trayIcon = icon; - _systemIcon = systemIcon; - _themeName = iconThemeName; - _monochrome = monochrome; - _count = counterSlice(counter); - _muted = muted; -} - QSize IconGraphic::dprSize(const QImage &image) const { return image.size() / image.devicePixelRatio(); } -QIcon IconGraphic::trayIcon( - const QIcon &systemIcon, - const QString &iconThemeName, - bool monochrome, - int counter, - bool muted) { - if (!isRefreshNeeded(systemIcon, iconThemeName, counter, muted)) { +void IconGraphic::updateState() { + _new.iconThemeName = QIcon::themeName(); + _new.monochrome = Core::App().settings().trayIconMonochrome(); + _new.counter = Core::App().unreadBadge(); + _new.muted = Core::App().unreadBadgeMuted(); + _new.systemIcon = systemIcon(); +} + +bool IconGraphic::isRefreshNeeded() const { + return _trayIcon.isNull() + || _new.iconThemeName != _current.iconThemeName + || _new.systemIcon.name() != _current.systemIcon.name() + || (_new.systemIcon.name() != PanelIconName(_new.counter, _new.muted) + ? _new.muted != _current.muted + || counterSlice(_new.counter) != counterSlice( + _current.counter) + : false); +} + +QIcon IconGraphic::trayIcon() { + if (!isRefreshNeeded()) { return _trayIcon; } - if (systemIcon.name() == PanelIconName(counter, muted)) { - updateIconRegenerationNeeded( - systemIcon, - systemIcon, - iconThemeName, - monochrome, - counter, - muted); + const auto guard = gsl::finally([&] { + _current = _new; + }); - return systemIcon; + if (_new.systemIcon.name() == PanelIconName( + _new.counter, + _new.muted)) { + _trayIcon = _new.systemIcon; + return _trayIcon; } QIcon result; - for (const auto iconSize : _iconSizes) { auto ¤tImageBack = _imageBack[iconSize]; const auto desiredSize = QSize(iconSize, iconSize); if (currentImageBack.isNull() - || iconThemeName != _themeName - || systemIcon.name() != _systemIcon.name()) { - if (!systemIcon.isNull()) { + || _new.iconThemeName != _current.iconThemeName + || _new.systemIcon.name() != _current.systemIcon.name()) { + if (!_new.systemIcon.isNull()) { // We can't use QIcon::actualSize here // since it works incorrectly with svg icon themes - currentImageBack = systemIcon + currentImageBack = _new.systemIcon .pixmap(desiredSize) .toImage(); @@ -202,7 +168,8 @@ QIcon IconGraphic::trayIcon( // if current icon theme is not a svg one, Qt can return // a pixmap that less in size even if there are a bigger one if (firstAttemptSize.width() < desiredSize.width()) { - const auto availableSizes = systemIcon.availableSizes(); + const auto availableSizes + = _new.systemIcon.availableSizes(); const auto biggestSize = ranges::max_element( availableSizes, @@ -210,7 +177,7 @@ QIcon IconGraphic::trayIcon( &QSize::width); if (biggestSize->width() > firstAttemptSize.width()) { - currentImageBack = systemIcon + currentImageBack = _new.systemIcon .pixmap(*biggestSize) .toImage(); } @@ -227,24 +194,17 @@ QIcon IconGraphic::trayIcon( } } - result.addPixmap(Ui::PixmapFromImage(counter > 0 + result.addPixmap(Ui::PixmapFromImage(_new.counter > 0 ? Window::WithSmallCounter(std::move(currentImageBack), { .size = iconSize, - .count = counter, - .bg = muted ? st::trayCounterBgMute : st::trayCounterBg, + .count = _new.counter, + .bg = _new.muted ? st::trayCounterBgMute : st::trayCounterBg, .fg = st::trayCounterFg, }) : std::move(currentImageBack))); } - updateIconRegenerationNeeded( - result, - systemIcon, - iconThemeName, - monochrome, - counter, - muted); - - return result; + _trayIcon = result; + return _trayIcon; } class TrayEventFilter final : public QObject { @@ -321,22 +281,8 @@ void Tray::createIcon() { }); }; - const auto iconThemeName = QIcon::themeName(); - const auto monochrome = Core::App().settings().trayIconMonochrome(); - const auto counter = Core::App().unreadBadge(); - const auto muted = Core::App().unreadBadgeMuted(); - _icon = base::make_unique_q(nullptr); - _icon->setIcon(_iconGraphic->trayIcon( - _iconGraphic->systemIcon( - iconThemeName, - monochrome, - counter, - muted), - iconThemeName, - monochrome, - counter, - muted)); + _icon->setIcon(_iconGraphic->trayIcon()); _icon->setToolTip(AppName.utf16()); using Reason = QSystemTrayIcon::ActivationReason; @@ -375,27 +321,10 @@ void Tray::updateIcon() { if (!_icon || !_iconGraphic) { return; } - const auto counter = Core::App().unreadBadge(); - const auto muted = Core::App().unreadBadgeMuted(); - const auto monochrome = Core::App().settings().trayIconMonochrome(); - const auto iconThemeName = QIcon::themeName(); - const auto systemIcon = _iconGraphic->systemIcon( - iconThemeName, - monochrome, - counter, - muted); - if (_iconGraphic->isRefreshNeeded( - systemIcon, - iconThemeName, - counter, - muted)) { - _icon->setIcon(_iconGraphic->trayIcon( - systemIcon, - iconThemeName, - monochrome, - counter, - muted)); + _iconGraphic->updateState(); + if (_iconGraphic->isRefreshNeeded()) { + _icon->setIcon(_iconGraphic->trayIcon()); } } From c672f105d3cbf1344571250af625f6e20e2e333a Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 23 Feb 2025 06:53:21 +0400 Subject: [PATCH 015/103] IconGraphic::isCounterNeeded helper for Linux tray --- Telegram/SourceFiles/platform/linux/tray_linux.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/tray_linux.cpp b/Telegram/SourceFiles/platform/linux/tray_linux.cpp index 3680f3751..5e1771871 100644 --- a/Telegram/SourceFiles/platform/linux/tray_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/tray_linux.cpp @@ -58,6 +58,7 @@ private: }; [[nodiscard]] QIcon systemIcon() const; + [[nodiscard]] bool isCounterNeeded(const State &state) const; [[nodiscard]] int counterSlice(int counter) const; [[nodiscard]] QSize dprSize(const QImage &image) const; @@ -103,6 +104,12 @@ QIcon IconGraphic::systemIcon() const { return QIcon(); } +bool IconGraphic::isCounterNeeded(const State &state) const { + return state.systemIcon.name() != PanelIconName( + state.counter, + state.muted); +} + int IconGraphic::counterSlice(int counter) const { return (counter >= 1000) ? (1000 + (counter % 100)) @@ -125,7 +132,7 @@ bool IconGraphic::isRefreshNeeded() const { return _trayIcon.isNull() || _new.iconThemeName != _current.iconThemeName || _new.systemIcon.name() != _current.systemIcon.name() - || (_new.systemIcon.name() != PanelIconName(_new.counter, _new.muted) + || (isCounterNeeded(_new) ? _new.muted != _current.muted || counterSlice(_new.counter) != counterSlice( _current.counter) @@ -141,9 +148,7 @@ QIcon IconGraphic::trayIcon() { _current = _new; }); - if (_new.systemIcon.name() == PanelIconName( - _new.counter, - _new.muted)) { + if (!isCounterNeeded(_new)) { _trayIcon = _new.systemIcon; return _trayIcon; } From 6f230103828ab01efa5a0b27d4cf0b65718e7cc6 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 23 Feb 2025 04:04:05 +0000 Subject: [PATCH 016/103] Fix IconGraphic::counterSlice for Window::WithSmallCounter --- Telegram/SourceFiles/platform/linux/tray_linux.cpp | 4 ++-- Telegram/SourceFiles/window/main_window.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/tray_linux.cpp b/Telegram/SourceFiles/platform/linux/tray_linux.cpp index 5e1771871..2cc92f331 100644 --- a/Telegram/SourceFiles/platform/linux/tray_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/tray_linux.cpp @@ -111,8 +111,8 @@ bool IconGraphic::isCounterNeeded(const State &state) const { } int IconGraphic::counterSlice(int counter) const { - return (counter >= 1000) - ? (1000 + (counter % 100)) + return (counter >= 100) + ? (100 + (counter % 10)) : counter; } diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index f81a5784a..1db1575b2 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -260,8 +260,6 @@ QIcon CreateIcon(Main::Session *session, bool returnNullIfDefault) { } QImage GenerateCounterLayer(CounterLayerArgs &&args) { - // platform/linux/main_window_linux depends on count used the same - // way for all the same (count % 1000) values. const auto count = args.count.value(); const auto text = (count < 1000) ? QString::number(count) @@ -338,6 +336,8 @@ QImage GenerateCounterLayer(CounterLayerArgs &&args) { } QImage WithSmallCounter(QImage image, CounterLayerArgs &&args) { + // platform/linux/tray_linux depends on count used the same + // way for all the same (count % 100) values. const auto count = args.count.value(); const auto text = (count < 100) ? QString::number(count) From ec6862d31a9d513dc2647679efa8a7bef3710f2e Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 25 Feb 2025 02:17:06 +0000 Subject: [PATCH 017/103] Communicate PiP window margins to the OS --- Telegram/SourceFiles/media/view/media_view_pip.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Telegram/SourceFiles/media/view/media_view_pip.cpp b/Telegram/SourceFiles/media/view/media_view_pip.cpp index e401e25a3..4e1d7c136 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.cpp +++ b/Telegram/SourceFiles/media/view/media_view_pip.cpp @@ -362,6 +362,7 @@ void PipPanel::init() { ) | rpl::filter(rpl::mappers::_1) | rpl::start_with_next([=] { // Workaround Qt's forced transient parent. Ui::Platform::ClearTransientParent(widget()); + Ui::Platform::SetWindowMargins(widget(), _padding); }, rp()->lifetime()); rp()->screenValue( @@ -878,6 +879,9 @@ void PipPanel::updateDecorations() { _padding = padding; _useTransparency = use; widget()->setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency); + if (widget()->windowHandle()) { + Ui::Platform::SetWindowMargins(widget(), _padding); + } setGeometry(newGeometry); update(); } From 4df90cfb9e16e604259e3cc93d450a696473ee83 Mon Sep 17 00:00:00 2001 From: davidholio Date: Mon, 24 Feb 2025 23:39:56 +0100 Subject: [PATCH 018/103] Make sure caption items can only be interacted if not in video fullscreen. --- Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index e752cc846..f50654f20 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -6102,7 +6102,7 @@ void OverlayWidget::updateOver(QPoint pos) { auto textState = _saveMsgText.getState(pos - _saveMsg.topLeft() - QPoint(st::mediaviewSaveMsgPadding.left(), st::mediaviewSaveMsgPadding.top()), _saveMsg.width() - st::mediaviewSaveMsgPadding.left() - st::mediaviewSaveMsgPadding.right()); lnk = textState.link; lnkhost = this; - } else if (_captionRect.contains(pos)) { + } else if (_captionRect.contains(pos) && !_fullScreenVideo) { auto request = Ui::Text::StateRequestElided(); const auto lineHeight = st::mediaviewCaptionStyle.font->height; request.lines = _captionRect.height() / lineHeight; From d5d12543933deec9a2eb8b09b992cbc88c57a3b8 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 26 Feb 2025 15:41:39 +0000 Subject: [PATCH 019/103] Destroy NotificationData signal connections with rpl::lifetme --- .../linux/notifications_manager_linux.cpp | 53 ++++++++----------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 9ee2dcb21..5a95c8de6 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -156,8 +156,6 @@ public: NotificationData(NotificationData &&other) = delete; NotificationData &operator=(NotificationData &&other) = delete; - ~NotificationData(); - void show(); void close(); void setImage(QImage image); @@ -181,10 +179,7 @@ private: std::string _imageKey; uint _notificationId = 0; - ulong _actionInvokedSignalId = 0; - ulong _activationTokenSignalId = 0; - ulong _notificationRepliedSignalId = 0; - ulong _notificationClosedSignalId = 0; + rpl::lifetime _lifetime; }; @@ -315,7 +310,7 @@ bool NotificationData::init(const Info &info) { _actions.push_back( tr::lng_notification_reply(tr::now).toStdString()); - _notificationRepliedSignalId + const auto notificationRepliedSignalId = _interface.signal_notification_replied().connect([=]( XdgNotifications::Notifications, uint id, @@ -328,10 +323,14 @@ bool NotificationData::init(const Info &info) { } }); }); + + _lifetime.add([=] { + _interface.disconnect(notificationRepliedSignalId); + }); } - _actionInvokedSignalId = _interface.signal_action_invoked().connect( - [=]( + const auto actionInvokedSignalId + = _interface.signal_action_invoked().connect([=]( XdgNotifications::Notifications, uint id, std::string actionName) { @@ -346,7 +345,11 @@ bool NotificationData::init(const Info &info) { }); }); - _activationTokenSignalId + _lifetime.add([=] { + _interface.disconnect(actionInvokedSignalId); + }); + + const auto activationTokenSignalId = _interface.signal_activation_token().connect([=]( XdgNotifications::Notifications, uint id, @@ -355,6 +358,10 @@ bool NotificationData::init(const Info &info) { GLib::setenv("XDG_ACTIVATION_TOKEN", token, true); } }); + + _lifetime.add([=] { + _interface.disconnect(activationTokenSignalId); + }); } if (HasCapability("action-icons")) { @@ -392,7 +399,7 @@ bool NotificationData::init(const Info &info) { _hints.insert_value("desktop-entry", GLib::Variant::new_string( QGuiApplication::desktopFileName().toStdString())); - _notificationClosedSignalId = + const auto notificationClosedSignalId = _interface.signal_notification_closed().connect([=]( XdgNotifications::Notifications, uint id, @@ -418,29 +425,13 @@ bool NotificationData::init(const Info &info) { }); }); + _lifetime.add([=] { + _interface.disconnect(notificationClosedSignalId); + }); + return true; } -NotificationData::~NotificationData() { - if (_interface) { - if (_actionInvokedSignalId != 0) { - _interface.disconnect(_actionInvokedSignalId); - } - - if (_activationTokenSignalId != 0) { - _interface.disconnect(_activationTokenSignalId); - } - - if (_notificationRepliedSignalId != 0) { - _interface.disconnect(_notificationRepliedSignalId); - } - - if (_notificationClosedSignalId != 0) { - _interface.disconnect(_notificationClosedSignalId); - } - } -} - void NotificationData::show() { if (_application && _notification) { _application.send_notification(_guid, _notification); From 86f7d09d31315b19464cbfa0cb347d2715648396 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 26 Feb 2025 18:13:10 +0000 Subject: [PATCH 020/103] Pass notification icon name inline --- .../platform/linux/notifications_manager_linux.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 5a95c8de6..d67fbc68d 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -441,11 +441,6 @@ void NotificationData::show() { // a hack for snap's activation restriction const auto weak = base::make_weak(this); StartServiceAsync(_proxy.get_connection(), crl::guard(weak, [=] { - const auto iconName = _imageKey.empty() - || !_hints.lookup_value(_imageKey) - ? base::IconName().toStdString() - : std::string(); - auto actions = _actions | ranges::views::transform(&std::string::c_str) | ranges::to_vector; @@ -476,7 +471,9 @@ void NotificationData::show() { _interface.gobj_(), AppName.data(), 0, - iconName.c_str(), + (_imageKey.empty() || !_hints.lookup_value(_imageKey) + ? base::IconName().toStdString() + : std::string()).c_str(), _title.c_str(), _body.c_str(), actions.data(), From 3569615b216166d8ac2f150317041ddac2d58b3e Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 26 Feb 2025 18:13:46 +0000 Subject: [PATCH 021/103] Use gi::cstring for notification actions --- .../linux/notifications_manager_linux.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index d67fbc68d..ff3535969 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -174,7 +174,7 @@ private: XdgNotifications::Notifications _interface; std::string _title; std::string _body; - std::vector _actions; + std::vector _actions; GLib::VariantDict _hints; std::string _imageKey; @@ -362,6 +362,8 @@ bool NotificationData::init(const Info &info) { _lifetime.add([=] { _interface.disconnect(activationTokenSignalId); }); + + _actions.push_back({}); } if (HasCapability("action-icons")) { @@ -441,11 +443,6 @@ void NotificationData::show() { // a hack for snap's activation restriction const auto weak = base::make_weak(this); StartServiceAsync(_proxy.get_connection(), crl::guard(weak, [=] { - auto actions = _actions - | ranges::views::transform(&std::string::c_str) - | ranges::to_vector; - actions.push_back(nullptr); - const auto callbackWrap = gi::unwrap( Gio::AsyncReadyCallback( crl::guard(weak, [=](GObject::Object, Gio::AsyncResult res) { @@ -476,7 +473,11 @@ void NotificationData::show() { : std::string()).c_str(), _title.c_str(), _body.c_str(), - actions.data(), + !_actions.empty() + ? (_actions + | ranges::views::transform(&gi::cstring::c_str) + | ranges::to_vector).data() + : nullptr, _hints.end().gobj_(), -1, nullptr, From 87452706efa8c3c1c316102bee198aae8ef4df87 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 26 Feb 2025 18:14:26 +0000 Subject: [PATCH 022/103] Remove unused has_weak_ptr from Manager::Private --- .../SourceFiles/platform/linux/notifications_manager_linux.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index ff3535969..fb819163c 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -540,7 +540,7 @@ void NotificationData::setImage(QImage image) { } // namespace -class Manager::Private : public base::has_weak_ptr { +class Manager::Private { public: explicit Private(not_null manager); From 93615fef65b89b68e6e4d21f3406ec93a4725b5f Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 26 Feb 2025 18:16:45 +0000 Subject: [PATCH 023/103] Revert "Check whether notification image has alpha channel" This reverts commit cee593c423a63ba8fb47b2fb160303ec87813655. Avatars couldn't be opaque anyway while this simplifies porting out of NotificationData --- .../platform/linux/notifications_manager_linux.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index fb819163c..7b8405967 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -516,19 +516,14 @@ void NotificationData::setImage(QImage image) { return; } - if (image.hasAlphaChannel()) { - image.convertTo(QImage::Format_RGBA8888); - } else { - image.convertTo(QImage::Format_RGB888); - } - + image.convertTo(QImage::Format_RGBA8888); _hints.insert_value(_imageKey, GLib::Variant::new_tuple({ GLib::Variant::new_int32(image.width()), GLib::Variant::new_int32(image.height()), GLib::Variant::new_int32(image.bytesPerLine()), - GLib::Variant::new_boolean(image.hasAlphaChannel()), + GLib::Variant::new_boolean(true), GLib::Variant::new_int32(8), - GLib::Variant::new_int32(image.hasAlphaChannel() ? 4 : 3), + GLib::Variant::new_int32(4), GLib::Variant::new_from_data( GLib::VariantType::new_("ay"), reinterpret_cast(image.constBits()), From 892db55ae140de606dd0a6b1c9e4c174f2e0be69 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 26 Feb 2025 18:36:29 +0000 Subject: [PATCH 024/103] Get rid of NotificationData::init --- .../linux/notifications_manager_linux.cpp | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 7b8405967..a45649bf6 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -147,9 +147,8 @@ public: NotificationData( not_null manager, XdgNotifications::NotificationsProxy proxy, - NotificationId id); - - [[nodiscard]] bool init(const Info &info); + NotificationId id, + const Info &info); NotificationData(const NotificationData &other) = delete; NotificationData &operator=(const NotificationData &other) = delete; @@ -188,7 +187,8 @@ using Notification = std::unique_ptr; NotificationData::NotificationData( not_null manager, XdgNotifications::NotificationsProxy proxy, - NotificationId id) + NotificationId id, + const Info &info) : _manager(manager) , _id(id) , _sounds(cWorkingDir() + u"tdata/audio_cache"_q) @@ -200,9 +200,6 @@ NotificationData::NotificationData( , _interface(proxy) , _hints(GLib::VariantDict::new_()) , _imageKey(GetImageKey()) { -} - -bool NotificationData::init(const Info &info) { const auto &title = info.title; const auto &subtitle = info.subtitle; @@ -268,12 +265,6 @@ bool NotificationData::init(const Info &info) { "app.notification-mark-as-read", notificationVariant); } - - return true; - } - - if (!_interface) { - return false; } const auto &text = info.message; @@ -430,8 +421,6 @@ bool NotificationData::init(const Info &info) { _lifetime.add([=] { _interface.disconnect(notificationClosedSignalId); }); - - return true; } void NotificationData::show() { @@ -816,11 +805,8 @@ void Manager::Private::showNotification( auto notification = std::make_unique( _manager, _proxy, - notificationId); - const auto inited = notification->init(info); - if (!inited) { - return; - } + notificationId, + info); if (!options.hideNameAndPhoto) { notification->setImage( From b07d3c540312982bd95b7896d4371e11ad618f26 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 26 Feb 2025 13:46:10 +0000 Subject: [PATCH 025/103] Decouple GNotification from NotificationData --- .../linux/notifications_manager_linux.cpp | 302 ++++++++++-------- 1 file changed, 176 insertions(+), 126 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index a45649bf6..c8561ba18 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -165,10 +165,6 @@ private: Media::Audio::LocalDiskCache _sounds; - Gio::Application _application; - Gio::Notification _notification; - const std::string _guid; - XdgNotifications::NotificationsProxy _proxy; XdgNotifications::Notifications _interface; std::string _title; @@ -192,81 +188,12 @@ NotificationData::NotificationData( : _manager(manager) , _id(id) , _sounds(cWorkingDir() + u"tdata/audio_cache"_q) -, _application(UseGNotification() - ? Gio::Application::get_default() - : nullptr) -, _guid(_application ? std::string(Gio::dbus_generate_guid()) : std::string()) , _proxy(proxy) , _interface(proxy) , _hints(GLib::VariantDict::new_()) , _imageKey(GetImageKey()) { const auto &title = info.title; const auto &subtitle = info.subtitle; - - if (_application) { - _notification = Gio::Notification::new_( - subtitle.isEmpty() - ? title.toStdString() - : subtitle.toStdString() + " (" + title.toStdString() + ')'); - - _notification.set_body(info.message.toStdString()); - - _notification.set_icon( - Gio::ThemedIcon::new_(base::IconName().toStdString())); - - // for chat messages, according to - // https://docs.gtk.org/gio/enum.NotificationPriority.html - _notification.set_priority(Gio::NotificationPriority::HIGH_); - - // glib 2.70+, we keep glib 2.56+ compatibility - static const auto set_category = [] { - // reset dlerror after dlsym call - const auto guard = gsl::finally([] { dlerror(); }); - return reinterpret_cast( - dlsym(RTLD_DEFAULT, "g_notification_set_category")); - }(); - - if (set_category) { - set_category(_notification.gobj_(), "im.received"); - } - - const auto peer = info.peer; - - const auto notificationVariant = GLib::Variant::new_array({ - GLib::Variant::new_dict_entry( - GLib::Variant::new_string("session"), - GLib::Variant::new_variant( - GLib::Variant::new_uint64(peer->session().uniqueId()))), - GLib::Variant::new_dict_entry( - GLib::Variant::new_string("peer"), - GLib::Variant::new_variant( - GLib::Variant::new_uint64(peer->id.value))), - GLib::Variant::new_dict_entry( - GLib::Variant::new_string("peer"), - GLib::Variant::new_variant( - GLib::Variant::new_uint64(peer->id.value))), - GLib::Variant::new_dict_entry( - GLib::Variant::new_string("topic"), - GLib::Variant::new_variant( - GLib::Variant::new_int64(info.topicRootId.bare))), - GLib::Variant::new_dict_entry( - GLib::Variant::new_string("msgid"), - GLib::Variant::new_variant( - GLib::Variant::new_int64(info.itemId.bare))), - }); - - _notification.set_default_action_and_target( - "app.notification-activate", - notificationVariant); - - if (!info.options.hideMarkAsRead) { - _notification.add_button_with_target( - tr::lng_context_mark_read(tr::now).toStdString(), - "app.notification-mark-as-read", - notificationVariant); - } - } - const auto &text = info.message; if (HasCapability("body-markup")) { _title = title.toStdString(); @@ -424,11 +351,6 @@ NotificationData::NotificationData( } void NotificationData::show() { - if (_application && _notification) { - _application.send_notification(_guid, _notification); - return; - } - // a hack for snap's activation restriction const auto weak = base::make_weak(this); StartServiceAsync(_proxy.get_connection(), crl::guard(weak, [=] { @@ -476,31 +398,11 @@ void NotificationData::show() { } void NotificationData::close() { - if (_application) { - _application.withdraw_notification(_guid); - } else { - _interface.call_close_notification(_notificationId, nullptr); - } + _interface.call_close_notification(_notificationId, nullptr); _manager->clearNotification(_id); } void NotificationData::setImage(QImage image) { - if (_notification) { - QByteArray imageData; - QBuffer buffer(&imageData); - buffer.open(QIODevice::WriteOnly); - image.save(&buffer, "PNG"); - - _notification.set_icon( - Gio::BytesIcon::new_( - GLib::Bytes::new_with_free_func( - reinterpret_cast(imageData.constData()), - imageData.size(), - [imageData] {}))); - - return; - } - if (_imageKey.empty()) { return; } @@ -548,8 +450,10 @@ private: base::flat_map< ContextId, - base::flat_map> _notifications; + base::flat_map>> _notifications; + Gio::Application _application; XdgNotifications::NotificationsProxy _proxy; XdgNotifications::Notifications _interface; rpl::lifetime _lifetime; @@ -699,7 +603,10 @@ void Create(Window::Notifications::System *system) { } Manager::Private::Private(not_null manager) -: _manager(manager) { +: _manager(manager) +, _application(UseGNotification() + ? Gio::Application::get_default() + : nullptr) { const auto &serverInformation = CurrentServerInformation; if (!serverInformation.name.empty()) { @@ -732,7 +639,7 @@ Manager::Private::Private(not_null manager) }).c_str())); } - if (auto actionMap = Gio::ActionMap(Gio::Application::get_default())) { + if (auto actionMap = Gio::ActionMap(_application)) { const auto dictToNotificationId = [](GLib::VariantDict dict) { return NotificationId{ .contextId = ContextId{ @@ -802,15 +709,98 @@ void Manager::Private::showNotification( .contextId = key, .msgId = info.itemId, }; - auto notification = std::make_unique( - _manager, - _proxy, - notificationId, - info); + auto notification = _application + ? std::variant( + Gio::Notification::new_( + info.subtitle.isEmpty() + ? info.title.toStdString() + : info.subtitle.toStdString() + + " (" + info.title.toStdString() + ')')) + : std::variant( + std::make_unique( + _manager, + _proxy, + notificationId, + info)); + + if (const auto ptr = std::get_if(¬ification)) { + auto ¬ification = *ptr; + + notification.set_body(info.message.toStdString()); + + notification.set_icon( + Gio::ThemedIcon::new_(base::IconName().toStdString())); + + // for chat messages, according to + // https://docs.gtk.org/gio/enum.NotificationPriority.html + notification.set_priority(Gio::NotificationPriority::HIGH_); + + // glib 2.70+, we keep glib 2.56+ compatibility + static const auto set_category = [] { + // reset dlerror after dlsym call + const auto guard = gsl::finally([] { dlerror(); }); + return reinterpret_cast( + dlsym(RTLD_DEFAULT, "g_notification_set_category")); + }(); + + if (set_category) { + set_category(notification.gobj_(), "im.received"); + } + + const auto notificationVariant = GLib::Variant::new_array({ + GLib::Variant::new_dict_entry( + GLib::Variant::new_string("session"), + GLib::Variant::new_variant( + GLib::Variant::new_uint64(peer->session().uniqueId()))), + GLib::Variant::new_dict_entry( + GLib::Variant::new_string("peer"), + GLib::Variant::new_variant( + GLib::Variant::new_uint64(peer->id.value))), + GLib::Variant::new_dict_entry( + GLib::Variant::new_string("peer"), + GLib::Variant::new_variant( + GLib::Variant::new_uint64(peer->id.value))), + GLib::Variant::new_dict_entry( + GLib::Variant::new_string("topic"), + GLib::Variant::new_variant( + GLib::Variant::new_int64(info.topicRootId.bare))), + GLib::Variant::new_dict_entry( + GLib::Variant::new_string("msgid"), + GLib::Variant::new_variant( + GLib::Variant::new_int64(info.itemId.bare))), + }); + + notification.set_default_action_and_target( + "app.notification-activate", + notificationVariant); + + if (!options.hideMarkAsRead) { + notification.add_button_with_target( + tr::lng_context_mark_read(tr::now).toStdString(), + "app.notification-mark-as-read", + notificationVariant); + } + } if (!options.hideNameAndPhoto) { - notification->setImage( - Window::Notifications::GenerateUserpic(peer, userpicView)); + v::match(notification, [&](Gio::Notification ¬ification) { + QByteArray imageData; + QBuffer buffer(&imageData); + buffer.open(QIODevice::WriteOnly); + Window::Notifications::GenerateUserpic(peer, userpicView).save( + &buffer, + "PNG"); + + notification.set_icon( + Gio::BytesIcon::new_( + GLib::Bytes::new_with_free_func( + reinterpret_cast(imageData.constData()), + imageData.size(), + [imageData] {}))); + }, [&](const Notification ¬ification) { + notification->setImage( + Window::Notifications::GenerateUserpic(peer, userpicView)); + }); } auto i = _notifications.find(key); @@ -819,25 +809,47 @@ void Manager::Private::showNotification( if (j != end(i->second)) { auto oldNotification = std::move(j->second); i->second.erase(j); - oldNotification->close(); + v::match(oldNotification, [&]( + const std::string &oldNotification) { + _application.withdraw_notification(oldNotification); + clearNotification(notificationId); + }, [&](const Notification &oldNotification) { + oldNotification->close(); + }); i = _notifications.find(key); } } if (i == end(_notifications)) { - i = _notifications.emplace( - key, - base::flat_map()).first; + i = _notifications.emplace(key).first; } - const auto j = i->second.emplace( - info.itemId, - std::move(notification)).first; - j->second->show(); + v::match(notification, [&](Gio::Notification ¬ification) { + const auto j = i->second.emplace( + info.itemId, + Gio::dbus_generate_guid()).first; + _application.send_notification( + v::get(j->second), + notification); + }, [&](Notification ¬ification) { + const auto j = i->second.emplace( + info.itemId, + std::move(notification)).first; + v::get(j->second)->show(); + }); } void Manager::Private::clearAll() { for (const auto &[key, notifications] : base::take(_notifications)) { for (const auto &[msgId, notification] : notifications) { - notification->close(); + v::match(notification, [&](const std::string ¬ification) { + const auto notificationId = NotificationId{ + .contextId = key, + .msgId = msgId, + }; + _application.withdraw_notification(notification); + clearNotification(notificationId); + }, [&](const Notification ¬ification) { + notification->close(); + }); } } } @@ -848,6 +860,10 @@ void Manager::Private::clearFromItem(not_null item) { .peerId = item->history()->peer->id, .topicRootId = item->topicRootId(), }; + const auto notificationId = NotificationId{ + .contextId = key, + .msgId = item->id, + }; const auto i = _notifications.find(key); if (i == _notifications.cend()) { return; @@ -861,7 +877,12 @@ void Manager::Private::clearFromItem(not_null item) { if (i->second.empty()) { _notifications.erase(i); } - taken->close(); + v::match(taken, [&](const std::string &taken) { + _application.withdraw_notification(taken); + clearNotification(notificationId); + }, [&](const Notification &taken) { + taken->close(); + }); } void Manager::Private::clearFromTopic(not_null topic) { @@ -875,7 +896,16 @@ void Manager::Private::clearFromTopic(not_null topic) { _notifications.erase(i); for (const auto &[msgId, notification] : temp) { - notification->close(); + v::match(notification, [&](const std::string ¬ification) { + const auto notificationId = NotificationId{ + .contextId = key, + .msgId = msgId, + }; + _application.withdraw_notification(notification); + clearNotification(notificationId); + }, [&](const Notification ¬ification) { + notification->close(); + }); } } } @@ -883,10 +913,11 @@ void Manager::Private::clearFromTopic(not_null topic) { void Manager::Private::clearFromHistory(not_null history) { const auto sessionId = history->session().uniqueId(); const auto peerId = history->peer->id; - auto i = _notifications.lower_bound(ContextId{ + const auto key = ContextId{ .sessionId = sessionId, .peerId = peerId, - }); + }; + auto i = _notifications.lower_bound(key); while (i != _notifications.cend() && i->first.sessionId == sessionId && i->first.peerId == peerId) { @@ -894,22 +925,41 @@ void Manager::Private::clearFromHistory(not_null history) { i = _notifications.erase(i); for (const auto &[msgId, notification] : temp) { - notification->close(); + v::match(notification, [&](const std::string ¬ification) { + const auto notificationId = NotificationId{ + .contextId = key, + .msgId = msgId, + }; + _application.withdraw_notification(notification); + clearNotification(notificationId); + }, [&](const Notification ¬ification) { + notification->close(); + }); } } } void Manager::Private::clearFromSession(not_null session) { const auto sessionId = session->uniqueId(); - auto i = _notifications.lower_bound(ContextId{ + const auto key = ContextId{ .sessionId = sessionId, - }); + }; + auto i = _notifications.lower_bound(key); while (i != _notifications.cend() && i->first.sessionId == sessionId) { const auto temp = base::take(i->second); i = _notifications.erase(i); for (const auto &[msgId, notification] : temp) { - notification->close(); + v::match(notification, [&](const std::string ¬ification) { + const auto notificationId = NotificationId{ + .contextId = key, + .msgId = msgId, + }; + _application.withdraw_notification(notification); + clearNotification(notificationId); + }, [&](const Notification ¬ification) { + notification->close(); + }); } } } From a8d1eadfbf9dabd7bb9cb21bbb5f1f40e2ebcb37 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 26 Feb 2025 18:07:19 +0000 Subject: [PATCH 026/103] Turn NotificationData into a struct --- .../linux/notifications_manager_linux.cpp | 610 ++++++++---------- .../linux/notifications_manager_linux.h | 1 - 2 files changed, 279 insertions(+), 332 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index c8561ba18..4028f4f52 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -139,291 +139,6 @@ bool UseGNotification() { return KSandbox::isFlatpak() && !ServiceRegistered; } -class NotificationData final : public base::has_weak_ptr { -public: - using NotificationId = Window::Notifications::Manager::NotificationId; - using Info = Window::Notifications::NativeManager::NotificationInfo; - - NotificationData( - not_null manager, - XdgNotifications::NotificationsProxy proxy, - NotificationId id, - const Info &info); - - NotificationData(const NotificationData &other) = delete; - NotificationData &operator=(const NotificationData &other) = delete; - NotificationData(NotificationData &&other) = delete; - NotificationData &operator=(NotificationData &&other) = delete; - - void show(); - void close(); - void setImage(QImage image); - -private: - const not_null _manager; - NotificationId _id; - - Media::Audio::LocalDiskCache _sounds; - - XdgNotifications::NotificationsProxy _proxy; - XdgNotifications::Notifications _interface; - std::string _title; - std::string _body; - std::vector _actions; - GLib::VariantDict _hints; - std::string _imageKey; - - uint _notificationId = 0; - rpl::lifetime _lifetime; - -}; - -using Notification = std::unique_ptr; - -NotificationData::NotificationData( - not_null manager, - XdgNotifications::NotificationsProxy proxy, - NotificationId id, - const Info &info) -: _manager(manager) -, _id(id) -, _sounds(cWorkingDir() + u"tdata/audio_cache"_q) -, _proxy(proxy) -, _interface(proxy) -, _hints(GLib::VariantDict::new_()) -, _imageKey(GetImageKey()) { - const auto &title = info.title; - const auto &subtitle = info.subtitle; - const auto &text = info.message; - if (HasCapability("body-markup")) { - _title = title.toStdString(); - - _body = subtitle.isEmpty() - ? text.toHtmlEscaped().toStdString() - : u"%1\n%2"_q.arg( - subtitle.toHtmlEscaped(), - text.toHtmlEscaped()).toStdString(); - } else { - _title = subtitle.isEmpty() - ? title.toStdString() - : subtitle.toStdString() + " (" + title.toStdString() + ')'; - - _body = text.toStdString(); - } - - if (HasCapability("actions")) { - _actions.push_back("default"); - _actions.push_back(tr::lng_open_link(tr::now).toStdString()); - - if (!info.options.hideMarkAsRead) { - // icon name according to https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html - _actions.push_back("mail-mark-read"); - _actions.push_back( - tr::lng_context_mark_read(tr::now).toStdString()); - } - - if (HasCapability("inline-reply") - && !info.options.hideReplyButton) { - _actions.push_back("inline-reply"); - _actions.push_back( - tr::lng_notification_reply(tr::now).toStdString()); - - const auto notificationRepliedSignalId - = _interface.signal_notification_replied().connect([=]( - XdgNotifications::Notifications, - uint id, - std::string text) { - Core::Sandbox::Instance().customEnterFromEventLoop([&] { - if (id == _notificationId) { - _manager->notificationReplied( - _id, - { QString::fromStdString(text), {} }); - } - }); - }); - - _lifetime.add([=] { - _interface.disconnect(notificationRepliedSignalId); - }); - } - - const auto actionInvokedSignalId - = _interface.signal_action_invoked().connect([=]( - XdgNotifications::Notifications, - uint id, - std::string actionName) { - Core::Sandbox::Instance().customEnterFromEventLoop([&] { - if (id == _notificationId) { - if (actionName == "default") { - _manager->notificationActivated(_id); - } else if (actionName == "mail-mark-read") { - _manager->notificationReplied(_id, {}); - } - } - }); - }); - - _lifetime.add([=] { - _interface.disconnect(actionInvokedSignalId); - }); - - const auto activationTokenSignalId - = _interface.signal_activation_token().connect([=]( - XdgNotifications::Notifications, - uint id, - std::string token) { - if (id == _notificationId) { - GLib::setenv("XDG_ACTIVATION_TOKEN", token, true); - } - }); - - _lifetime.add([=] { - _interface.disconnect(activationTokenSignalId); - }); - - _actions.push_back({}); - } - - if (HasCapability("action-icons")) { - _hints.insert_value("action-icons", GLib::Variant::new_boolean(true)); - } - - if (HasCapability("sound")) { - const auto sound = info.sound - ? info.sound() - : Media::Audio::LocalSound(); - - const auto path = sound - ? _sounds.path(sound).toStdString() - : std::string(); - - if (!path.empty()) { - _hints.insert_value( - "sound-file", - GLib::Variant::new_string(path)); - } else { - _hints.insert_value( - "suppress-sound", - GLib::Variant::new_boolean(true)); - } - } - - if (HasCapability("x-canonical-append")) { - _hints.insert_value( - "x-canonical-append", - GLib::Variant::new_string("true")); - } - - _hints.insert_value("category", GLib::Variant::new_string("im.received")); - - _hints.insert_value("desktop-entry", GLib::Variant::new_string( - QGuiApplication::desktopFileName().toStdString())); - - const auto notificationClosedSignalId = - _interface.signal_notification_closed().connect([=]( - XdgNotifications::Notifications, - uint id, - uint reason) { - Core::Sandbox::Instance().customEnterFromEventLoop([&] { - /* - * From: https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html - * The reason the notification was closed - * 1 - The notification expired. - * 2 - The notification was dismissed by the user. - * 3 - The notification was closed by a call to CloseNotification. - * 4 - Undefined/reserved reasons. - * - * If the notification was dismissed by the user (reason == 2), the notification is not kept in notification history. - * We do not need to send a "CloseNotification" call later to clear it from history. - * Therefore we can drop the notification reference now. - * In all other cases we keep the notification reference so that we may clear the notification later from history, - * if the message for that notification is read (e.g. chat is opened or read from another device). - */ - if (id == _notificationId && reason == 2) { - _manager->clearNotification(_id); - } - }); - }); - - _lifetime.add([=] { - _interface.disconnect(notificationClosedSignalId); - }); -} - -void NotificationData::show() { - // a hack for snap's activation restriction - const auto weak = base::make_weak(this); - StartServiceAsync(_proxy.get_connection(), crl::guard(weak, [=] { - const auto callbackWrap = gi::unwrap( - Gio::AsyncReadyCallback( - crl::guard(weak, [=](GObject::Object, Gio::AsyncResult res) { - Core::Sandbox::Instance().customEnterFromEventLoop([&] { - const auto result = _interface.call_notify_finish( - res); - - if (!result) { - Gio::DBusErrorNS_::strip_remote_error( - result.error()); - LOG(("Native Notification Error: %1").arg( - result.error().message_().c_str())); - _manager->clearNotification(_id); - return; - } - - _notificationId = std::get<1>(*result); - }); - })), - gi::scope_async); - - xdg_notifications_notifications_call_notify( - _interface.gobj_(), - AppName.data(), - 0, - (_imageKey.empty() || !_hints.lookup_value(_imageKey) - ? base::IconName().toStdString() - : std::string()).c_str(), - _title.c_str(), - _body.c_str(), - !_actions.empty() - ? (_actions - | ranges::views::transform(&gi::cstring::c_str) - | ranges::to_vector).data() - : nullptr, - _hints.end().gobj_(), - -1, - nullptr, - &callbackWrap->wrapper, - callbackWrap); - })); -} - -void NotificationData::close() { - _interface.call_close_notification(_notificationId, nullptr); - _manager->clearNotification(_id); -} - -void NotificationData::setImage(QImage image) { - if (_imageKey.empty()) { - return; - } - - image.convertTo(QImage::Format_RGBA8888); - _hints.insert_value(_imageKey, GLib::Variant::new_tuple({ - GLib::Variant::new_int32(image.width()), - GLib::Variant::new_int32(image.height()), - GLib::Variant::new_int32(image.bytesPerLine()), - GLib::Variant::new_boolean(true), - GLib::Variant::new_int32(8), - GLib::Variant::new_int32(4), - GLib::Variant::new_from_data( - GLib::VariantType::new_("ay"), - reinterpret_cast(image.constBits()), - image.sizeInBytes(), - true, - [image] {}), - })); -} - } // namespace class Manager::Private { @@ -446,6 +161,12 @@ public: ~Private(); private: + struct NotificationData : public base::has_weak_ptr { + uint id = 0; + rpl::lifetime lifetime; + }; + using Notification = std::unique_ptr; + const not_null _manager; base::flat_map< @@ -456,6 +177,7 @@ private: Gio::Application _application; XdgNotifications::NotificationsProxy _proxy; XdgNotifications::Notifications _interface; + Media::Audio::LocalDiskCache _sounds; rpl::lifetime _lifetime; }; @@ -606,7 +328,8 @@ Manager::Private::Private(not_null manager) : _manager(manager) , _application(UseGNotification() ? Gio::Application::get_default() - : nullptr) { + : nullptr) +, _sounds(cWorkingDir() + u"tdata/audio_cache"_q) { const auto &serverInformation = CurrentServerInformation; if (!serverInformation.name.empty()) { @@ -716,16 +439,11 @@ void Manager::Private::showNotification( ? info.title.toStdString() : info.subtitle.toStdString() + " (" + info.title.toStdString() + ')')) - : std::variant( - std::make_unique( - _manager, - _proxy, - notificationId, - info)); - - if (const auto ptr = std::get_if(¬ification)) { - auto ¬ification = *ptr; + : std::variant(Notification{}); + std::vector actions; + auto hints = GLib::VariantDict::new_(); + v::match(notification, [&](Gio::Notification ¬ification) { notification.set_body(info.message.toStdString()); notification.set_icon( @@ -780,8 +498,157 @@ void Manager::Private::showNotification( "app.notification-mark-as-read", notificationVariant); } - } + }, [&](const Notification &owned) { + const auto notification = owned.get(); + if (HasCapability("actions")) { + actions.push_back("default"); + actions.push_back(tr::lng_open_link(tr::now).toStdString()); + + if (!options.hideMarkAsRead) { + // icon name according to https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html + actions.push_back("mail-mark-read"); + actions.push_back( + tr::lng_context_mark_read(tr::now).toStdString()); + } + + if (HasCapability("inline-reply") + && !options.hideReplyButton) { + actions.push_back("inline-reply"); + actions.push_back( + tr::lng_notification_reply(tr::now).toStdString()); + + const auto notificationRepliedSignalId + = _interface.signal_notification_replied().connect([=]( + XdgNotifications::Notifications, + uint id, + std::string text) { + Core::Sandbox::Instance().customEnterFromEventLoop( + [&] { + if (id == notification->id) { + _manager->notificationReplied( + notificationId, + { QString::fromStdString(text), {} }); + } + }); + }); + + notification->lifetime.add([=] { + _interface.disconnect(notificationRepliedSignalId); + }); + } + + const auto actionInvokedSignalId + = _interface.signal_action_invoked().connect([=]( + XdgNotifications::Notifications, + uint id, + std::string actionName) { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + if (id == notification->id) { + if (actionName == "default") { + _manager->notificationActivated( + notificationId); + } else if (actionName == "mail-mark-read") { + _manager->notificationReplied( + notificationId, + {}); + } + } + }); + }); + + notification->lifetime.add([=] { + _interface.disconnect(actionInvokedSignalId); + }); + + const auto activationTokenSignalId + = _interface.signal_activation_token().connect([=]( + XdgNotifications::Notifications, + uint id, + std::string token) { + if (id == notification->id) { + GLib::setenv("XDG_ACTIVATION_TOKEN", token, true); + } + }); + + notification->lifetime.add([=] { + _interface.disconnect(activationTokenSignalId); + }); + + actions.push_back({}); + } + + if (HasCapability("action-icons")) { + hints.insert_value( + "action-icons", + GLib::Variant::new_boolean(true)); + } + + if (HasCapability("sound")) { + const auto sound = info.sound + ? info.sound() + : Media::Audio::LocalSound(); + + const auto path = sound + ? _sounds.path(sound).toStdString() + : std::string(); + + if (!path.empty()) { + hints.insert_value( + "sound-file", + GLib::Variant::new_string(path)); + } else { + hints.insert_value( + "suppress-sound", + GLib::Variant::new_boolean(true)); + } + } + + if (HasCapability("x-canonical-append")) { + hints.insert_value( + "x-canonical-append", + GLib::Variant::new_string("true")); + } + + hints.insert_value( + "category", + GLib::Variant::new_string("im.received")); + + hints.insert_value("desktop-entry", GLib::Variant::new_string( + QGuiApplication::desktopFileName().toStdString())); + + const auto notificationClosedSignalId = + _interface.signal_notification_closed().connect([=]( + XdgNotifications::Notifications, + uint id, + uint reason) { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + /* + * From: https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html + * The reason the notification was closed + * 1 - The notification expired. + * 2 - The notification was dismissed by the user. + * 3 - The notification was closed by a call to CloseNotification. + * 4 - Undefined/reserved reasons. + * + * If the notification was dismissed by the user (reason == 2), the notification is not kept in notification history. + * We do not need to send a "CloseNotification" call later to clear it from history. + * Therefore we can drop the notification reference now. + * In all other cases we keep the notification reference so that we may clear the notification later from history, + * if the message for that notification is read (e.g. chat is opened or read from another device). + */ + if (id == notification->id && reason == 2) { + clearNotification(notificationId); + } + }); + }); + + notification->lifetime.add([=] { + _interface.disconnect(notificationClosedSignalId); + }); + }); + + const auto imageKey = GetImageKey(); if (!options.hideNameAndPhoto) { v::match(notification, [&](Gio::Notification ¬ification) { QByteArray imageData; @@ -798,8 +665,29 @@ void Manager::Private::showNotification( imageData.size(), [imageData] {}))); }, [&](const Notification ¬ification) { - notification->setImage( - Window::Notifications::GenerateUserpic(peer, userpicView)); + if (imageKey.empty()) { + return; + } + + const auto image = Window::Notifications::GenerateUserpic( + peer, + userpicView + ).convertToFormat(QImage::Format_RGBA8888); + + hints.insert_value(imageKey, GLib::Variant::new_tuple({ + GLib::Variant::new_int32(image.width()), + GLib::Variant::new_int32(image.height()), + GLib::Variant::new_int32(image.bytesPerLine()), + GLib::Variant::new_boolean(true), + GLib::Variant::new_int32(8), + GLib::Variant::new_int32(4), + GLib::Variant::new_from_data( + GLib::VariantType::new_("ay"), + reinterpret_cast(image.constBits()), + image.sizeInBytes(), + true, + [image] {}), + })); }); } @@ -812,10 +700,12 @@ void Manager::Private::showNotification( v::match(oldNotification, [&]( const std::string &oldNotification) { _application.withdraw_notification(oldNotification); - clearNotification(notificationId); }, [&](const Notification &oldNotification) { - oldNotification->close(); + _interface.call_close_notification( + oldNotification->id, + nullptr); }); + clearNotification(notificationId); i = _notifications.find(key); } } @@ -833,23 +723,85 @@ void Manager::Private::showNotification( const auto j = i->second.emplace( info.itemId, std::move(notification)).first; - v::get(j->second)->show(); + + const auto weak = base::make_weak( + v::get(j->second).get()); + + // work around snap's activation restriction + StartServiceAsync( + _proxy.get_connection(), + crl::guard(weak, [=]() mutable { + const auto hasBodyMarkup = HasCapability("body-markup"); + + const auto callbackWrap = gi::unwrap( + Gio::AsyncReadyCallback( + crl::guard(weak, [=]( + GObject::Object, + Gio::AsyncResult res) { + auto &sandbox = Core::Sandbox::Instance(); + sandbox.customEnterFromEventLoop([&] { + const auto result + = _interface.call_notify_finish(res); + + if (!result) { + Gio::DBusErrorNS_::strip_remote_error( + result.error()); + LOG(("Native Notification Error: %1").arg( + result.error().message_().c_str())); + clearNotification(notificationId); + return; + } + + weak->id = std::get<1>(*result); + }); + })), + gi::scope_async); + + xdg_notifications_notifications_call_notify( + _interface.gobj_(), + AppName.data(), + 0, + (imageKey.empty() || !hints.lookup_value(imageKey) + ? base::IconName().toStdString() + : std::string()).c_str(), + (hasBodyMarkup || info.subtitle.isEmpty() + ? info.title.toStdString() + : info.subtitle.toStdString() + + " (" + info.title.toStdString() + ')').c_str(), + (hasBodyMarkup + ? info.subtitle.isEmpty() + ? info.message.toHtmlEscaped().toStdString() + : u"%1\n%2"_q.arg( + info.subtitle.toHtmlEscaped(), + info.message.toHtmlEscaped()).toStdString() + : info.message.toStdString()).c_str(), + !actions.empty() + ? (actions + | ranges::views::transform(&gi::cstring::c_str) + | ranges::to_vector).data() + : nullptr, + hints.end().gobj_(), + -1, + nullptr, + &callbackWrap->wrapper, + callbackWrap); + })); }); } void Manager::Private::clearAll() { for (const auto &[key, notifications] : base::take(_notifications)) { for (const auto &[msgId, notification] : notifications) { + const auto notificationId = NotificationId{ + .contextId = key, + .msgId = msgId, + }; v::match(notification, [&](const std::string ¬ification) { - const auto notificationId = NotificationId{ - .contextId = key, - .msgId = msgId, - }; _application.withdraw_notification(notification); - clearNotification(notificationId); }, [&](const Notification ¬ification) { - notification->close(); + _interface.call_close_notification(notification->id, nullptr); }); + clearNotification(notificationId); } } } @@ -879,10 +831,10 @@ void Manager::Private::clearFromItem(not_null item) { } v::match(taken, [&](const std::string &taken) { _application.withdraw_notification(taken); - clearNotification(notificationId); }, [&](const Notification &taken) { - taken->close(); + _interface.call_close_notification(taken->id, nullptr); }); + clearNotification(notificationId); } void Manager::Private::clearFromTopic(not_null topic) { @@ -896,16 +848,16 @@ void Manager::Private::clearFromTopic(not_null topic) { _notifications.erase(i); for (const auto &[msgId, notification] : temp) { + const auto notificationId = NotificationId{ + .contextId = key, + .msgId = msgId, + }; v::match(notification, [&](const std::string ¬ification) { - const auto notificationId = NotificationId{ - .contextId = key, - .msgId = msgId, - }; _application.withdraw_notification(notification); - clearNotification(notificationId); }, [&](const Notification ¬ification) { - notification->close(); + _interface.call_close_notification(notification->id, nullptr); }); + clearNotification(notificationId); } } } @@ -925,16 +877,16 @@ void Manager::Private::clearFromHistory(not_null history) { i = _notifications.erase(i); for (const auto &[msgId, notification] : temp) { + const auto notificationId = NotificationId{ + .contextId = key, + .msgId = msgId, + }; v::match(notification, [&](const std::string ¬ification) { - const auto notificationId = NotificationId{ - .contextId = key, - .msgId = msgId, - }; _application.withdraw_notification(notification); - clearNotification(notificationId); }, [&](const Notification ¬ification) { - notification->close(); + _interface.call_close_notification(notification->id, nullptr); }); + clearNotification(notificationId); } } } @@ -950,16 +902,16 @@ void Manager::Private::clearFromSession(not_null session) { i = _notifications.erase(i); for (const auto &[msgId, notification] : temp) { + const auto notificationId = NotificationId{ + .contextId = key, + .msgId = msgId, + }; v::match(notification, [&](const std::string ¬ification) { - const auto notificationId = NotificationId{ - .contextId = key, - .msgId = msgId, - }; _application.withdraw_notification(notification); - clearNotification(notificationId); }, [&](const Notification ¬ification) { - notification->close(); + _interface.call_close_notification(notification->id, nullptr); }); + clearNotification(notificationId); } } } @@ -988,10 +940,6 @@ Manager::Manager(not_null system) , _private(std::make_unique(this)) { } -void Manager::clearNotification(NotificationId id) { - _private->clearNotification(id); -} - Manager::~Manager() = default; void Manager::doShowNativeNotification( diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h index ecf1ce0c0..8ab17f55b 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h @@ -15,7 +15,6 @@ namespace Notifications { class Manager : public Window::Notifications::NativeManager { public: Manager(not_null system); - void clearNotification(NotificationId id); ~Manager(); protected: From 86a294ce4b8f9be43f816c7fd6d4180b91758c96 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 28 Feb 2025 09:53:55 +0000 Subject: [PATCH 027/103] Subscribe to XdgNotifications signals on Manager initialization --- .../linux/notifications_manager_linux.cpp | 194 ++++++++++-------- 1 file changed, 104 insertions(+), 90 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 4028f4f52..3717b81ea 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -163,7 +163,6 @@ public: private: struct NotificationData : public base::has_weak_ptr { uint id = 0; - rpl::lifetime lifetime; }; using Notification = std::unique_ptr; @@ -416,6 +415,109 @@ Manager::Private::Private(not_null manager) void Manager::Private::init(XdgNotifications::NotificationsProxy proxy) { _proxy = proxy; _interface = proxy; + + if (_application || !_interface) { + return; + } + + const auto actionInvoked = _interface.signal_action_invoked().connect([=]( + XdgNotifications::Notifications, + uint id, + std::string actionName) { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + for (const auto &[key, notifications] : _notifications) { + for (const auto &[msgId, notification] : notifications) { + if (id == v::get(notification)->id) { + if (actionName == "default") { + _manager->notificationActivated({ key, msgId }); + } else if (actionName == "mail-mark-read") { + _manager->notificationReplied({ key, msgId }, {}); + } + return; + } + } + } + }); + }); + + _lifetime.add([=] { + _interface.disconnect(actionInvoked); + }); + + const auto replied = _interface.signal_notification_replied().connect([=]( + XdgNotifications::Notifications, + uint id, + std::string text) { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + for (const auto &[key, notifications] : _notifications) { + for (const auto &[msgId, notification] : notifications) { + if (id == v::get(notification)->id) { + _manager->notificationReplied( + { key, msgId }, + { QString::fromStdString(text), {} }); + return; + } + } + } + }); + }); + + _lifetime.add([=] { + _interface.disconnect(replied); + }); + + const auto tokenSignal = _interface.signal_activation_token().connect([=]( + XdgNotifications::Notifications, + uint id, + std::string token) { + for (const auto &[key, notifications] : _notifications) { + for (const auto &[msgId, notification] : notifications) { + if (id == v::get(notification)->id) { + GLib::setenv("XDG_ACTIVATION_TOKEN", token, true); + return; + } + } + } + }); + + _lifetime.add([=] { + _interface.disconnect(tokenSignal); + }); + + const auto closed = _interface.signal_notification_closed().connect([=]( + XdgNotifications::Notifications, + uint id, + uint reason) { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + for (const auto &[key, notifications] : _notifications) { + for (const auto &[msgId, notification] : notifications) { + /* + * From: https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html + * The reason the notification was closed + * 1 - The notification expired. + * 2 - The notification was dismissed by the user. + * 3 - The notification was closed by a call to CloseNotification. + * 4 - Undefined/reserved reasons. + * + * If the notification was dismissed by the user (reason == 2), the notification is not kept in notification history. + * We do not need to send a "CloseNotification" call later to clear it from history. + * Therefore we can drop the notification reference now. + * In all other cases we keep the notification reference so that we may clear the notification later from history, + * if the message for that notification is read (e.g. chat is opened or read from another device). + */ + if (id == v::get(notification)->id + && reason == 2) { + clearNotification({ key, msgId }); + return; + } + } + } + }); + }); + + _lifetime.add([=] { + _interface.disconnect(closed); + }); } void Manager::Private::showNotification( @@ -498,9 +600,7 @@ void Manager::Private::showNotification( "app.notification-mark-as-read", notificationVariant); } - }, [&](const Notification &owned) { - const auto notification = owned.get(); - + }, [&](const Notification ¬ification) { if (HasCapability("actions")) { actions.push_back("default"); actions.push_back(tr::lng_open_link(tr::now).toStdString()); @@ -517,64 +617,8 @@ void Manager::Private::showNotification( actions.push_back("inline-reply"); actions.push_back( tr::lng_notification_reply(tr::now).toStdString()); - - const auto notificationRepliedSignalId - = _interface.signal_notification_replied().connect([=]( - XdgNotifications::Notifications, - uint id, - std::string text) { - Core::Sandbox::Instance().customEnterFromEventLoop( - [&] { - if (id == notification->id) { - _manager->notificationReplied( - notificationId, - { QString::fromStdString(text), {} }); - } - }); - }); - - notification->lifetime.add([=] { - _interface.disconnect(notificationRepliedSignalId); - }); } - const auto actionInvokedSignalId - = _interface.signal_action_invoked().connect([=]( - XdgNotifications::Notifications, - uint id, - std::string actionName) { - Core::Sandbox::Instance().customEnterFromEventLoop([&] { - if (id == notification->id) { - if (actionName == "default") { - _manager->notificationActivated( - notificationId); - } else if (actionName == "mail-mark-read") { - _manager->notificationReplied( - notificationId, - {}); - } - } - }); - }); - - notification->lifetime.add([=] { - _interface.disconnect(actionInvokedSignalId); - }); - - const auto activationTokenSignalId - = _interface.signal_activation_token().connect([=]( - XdgNotifications::Notifications, - uint id, - std::string token) { - if (id == notification->id) { - GLib::setenv("XDG_ACTIVATION_TOKEN", token, true); - } - }); - - notification->lifetime.add([=] { - _interface.disconnect(activationTokenSignalId); - }); - actions.push_back({}); } @@ -616,36 +660,6 @@ void Manager::Private::showNotification( hints.insert_value("desktop-entry", GLib::Variant::new_string( QGuiApplication::desktopFileName().toStdString())); - - const auto notificationClosedSignalId = - _interface.signal_notification_closed().connect([=]( - XdgNotifications::Notifications, - uint id, - uint reason) { - Core::Sandbox::Instance().customEnterFromEventLoop([&] { - /* - * From: https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html - * The reason the notification was closed - * 1 - The notification expired. - * 2 - The notification was dismissed by the user. - * 3 - The notification was closed by a call to CloseNotification. - * 4 - Undefined/reserved reasons. - * - * If the notification was dismissed by the user (reason == 2), the notification is not kept in notification history. - * We do not need to send a "CloseNotification" call later to clear it from history. - * Therefore we can drop the notification reference now. - * In all other cases we keep the notification reference so that we may clear the notification later from history, - * if the message for that notification is read (e.g. chat is opened or read from another device). - */ - if (id == notification->id && reason == 2) { - clearNotification(notificationId); - } - }); - }); - - notification->lifetime.add([=] { - _interface.disconnect(notificationClosedSignalId); - }); }); const auto imageKey = GetImageKey(); From b2481ea6c1be94f6448de11eb07d9eedcbc5ff6d Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 1 Mar 2025 00:32:57 +0000 Subject: [PATCH 028/103] Update User-Agent for DNS to Chrome 133.0.0.0. --- .../SourceFiles/mtproto/details/mtproto_domain_resolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp index e18220968..c48f0f224 100644 --- a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp +++ b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp @@ -65,7 +65,7 @@ QByteArray DnsUserAgent() { static const auto kResult = QByteArray( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/131.0.0.0 Safari/537.36"); + "Chrome/133.0.0.0 Safari/537.36"); return kResult; } From 5dbe429e6b807b12ef143210cc3c9045aa767713 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 3 Mar 2025 10:33:38 +0000 Subject: [PATCH 029/103] Fix NotificationData initialization --- .../SourceFiles/platform/linux/notifications_manager_linux.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 3717b81ea..835aceffa 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -541,7 +541,8 @@ void Manager::Private::showNotification( ? info.title.toStdString() : info.subtitle.toStdString() + " (" + info.title.toStdString() + ')')) - : std::variant(Notification{}); + : std::variant( + std::make_unique()); std::vector actions; auto hints = GLib::VariantDict::new_(); From 66fc9b38df687bd4d1efeb339a49790d7f7a58e8 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 3 Mar 2025 10:47:02 +0000 Subject: [PATCH 030/103] Fix a race condition with asynchronous notification sending --- .../platform/linux/notifications_manager_linux.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 835aceffa..67abd46b8 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -141,7 +141,7 @@ bool UseGNotification() { } // namespace -class Manager::Private { +class Manager::Private : public base::has_weak_ptr { public: explicit Private(not_null manager); @@ -750,7 +750,7 @@ void Manager::Private::showNotification( const auto callbackWrap = gi::unwrap( Gio::AsyncReadyCallback( - crl::guard(weak, [=]( + crl::guard(this, [=]( GObject::Object, Gio::AsyncResult res) { auto &sandbox = Core::Sandbox::Instance(); @@ -767,6 +767,13 @@ void Manager::Private::showNotification( return; } + if (!weak) { + _interface.call_close_notification( + std::get<1>(*result), + nullptr); + return; + } + weak->id = std::get<1>(*result); }); })), From 9e12e18f90ba5a9b4d397a9feba15e16f7283a13 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 2 Mar 2025 11:29:02 +0000 Subject: [PATCH 031/103] Clean up unnecessary calls to Manager::Private::clearNotification It was added to replicate NotificationData::close but lots of places call it after the notification is already cleared --- .../linux/notifications_manager_linux.cpp | 40 +++---------------- 1 file changed, 5 insertions(+), 35 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 67abd46b8..b8c2f77b3 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -720,11 +720,8 @@ void Manager::Private::showNotification( oldNotification->id, nullptr); }); - clearNotification(notificationId); - i = _notifications.find(key); } - } - if (i == end(_notifications)) { + } else { i = _notifications.emplace(key).first; } v::match(notification, [&](Gio::Notification ¬ification) { @@ -814,16 +811,11 @@ void Manager::Private::showNotification( void Manager::Private::clearAll() { for (const auto &[key, notifications] : base::take(_notifications)) { for (const auto &[msgId, notification] : notifications) { - const auto notificationId = NotificationId{ - .contextId = key, - .msgId = msgId, - }; v::match(notification, [&](const std::string ¬ification) { _application.withdraw_notification(notification); }, [&](const Notification ¬ification) { _interface.call_close_notification(notification->id, nullptr); }); - clearNotification(notificationId); } } } @@ -834,10 +826,6 @@ void Manager::Private::clearFromItem(not_null item) { .peerId = item->history()->peer->id, .topicRootId = item->topicRootId(), }; - const auto notificationId = NotificationId{ - .contextId = key, - .msgId = item->id, - }; const auto i = _notifications.find(key); if (i == _notifications.cend()) { return; @@ -856,7 +844,6 @@ void Manager::Private::clearFromItem(not_null item) { }, [&](const Notification &taken) { _interface.call_close_notification(taken->id, nullptr); }); - clearNotification(notificationId); } void Manager::Private::clearFromTopic(not_null topic) { @@ -870,16 +857,11 @@ void Manager::Private::clearFromTopic(not_null topic) { _notifications.erase(i); for (const auto &[msgId, notification] : temp) { - const auto notificationId = NotificationId{ - .contextId = key, - .msgId = msgId, - }; v::match(notification, [&](const std::string ¬ification) { _application.withdraw_notification(notification); }, [&](const Notification ¬ification) { _interface.call_close_notification(notification->id, nullptr); }); - clearNotification(notificationId); } } } @@ -887,11 +869,10 @@ void Manager::Private::clearFromTopic(not_null topic) { void Manager::Private::clearFromHistory(not_null history) { const auto sessionId = history->session().uniqueId(); const auto peerId = history->peer->id; - const auto key = ContextId{ + auto i = _notifications.lower_bound(ContextId{ .sessionId = sessionId, .peerId = peerId, - }; - auto i = _notifications.lower_bound(key); + }); while (i != _notifications.cend() && i->first.sessionId == sessionId && i->first.peerId == peerId) { @@ -899,41 +880,30 @@ void Manager::Private::clearFromHistory(not_null history) { i = _notifications.erase(i); for (const auto &[msgId, notification] : temp) { - const auto notificationId = NotificationId{ - .contextId = key, - .msgId = msgId, - }; v::match(notification, [&](const std::string ¬ification) { _application.withdraw_notification(notification); }, [&](const Notification ¬ification) { _interface.call_close_notification(notification->id, nullptr); }); - clearNotification(notificationId); } } } void Manager::Private::clearFromSession(not_null session) { const auto sessionId = session->uniqueId(); - const auto key = ContextId{ + auto i = _notifications.lower_bound(ContextId{ .sessionId = sessionId, - }; - auto i = _notifications.lower_bound(key); + }); while (i != _notifications.cend() && i->first.sessionId == sessionId) { const auto temp = base::take(i->second); i = _notifications.erase(i); for (const auto &[msgId, notification] : temp) { - const auto notificationId = NotificationId{ - .contextId = key, - .msgId = msgId, - }; v::match(notification, [&](const std::string ¬ification) { _application.withdraw_notification(notification); }, [&](const Notification ¬ification) { _interface.call_close_notification(notification->id, nullptr); }); - clearNotification(notificationId); } } } From e99cb9bfb856dd7451fa3038048b8a2b1452f732 Mon Sep 17 00:00:00 2001 From: Eugene Date: Sat, 1 Mar 2025 19:03:51 +0300 Subject: [PATCH 032/103] Ensure policy check before creating Zone.Identifier for downloaded files on Windows --- .../platform/win/file_utilities_win.cpp | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Telegram/SourceFiles/platform/win/file_utilities_win.cpp b/Telegram/SourceFiles/platform/win/file_utilities_win.cpp index e07298811..f3cc6fb89 100644 --- a/Telegram/SourceFiles/platform/win/file_utilities_win.cpp +++ b/Telegram/SourceFiles/platform/win/file_utilities_win.cpp @@ -117,6 +117,28 @@ HBITMAP IconToBitmap(LPWSTR icon, int iconindex) { return (HBITMAP)CopyImage(result, IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_CREATEDIBSECTION); } +bool ShouldSaveZoneInformation() { + // Check if the "Do not preserve zone information in file attachments" policy is enabled. + const auto keyName = L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Attachments"; + const auto valueName = L"SaveZoneInformation"; + auto key = HKEY(); + auto result = RegOpenKeyEx(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &key); + if (result != ERROR_SUCCESS) { + // If the registry key cannot be opened, assume the default behavior: + // Windows preserves zone information for downloaded files. + return true; + } + + DWORD value = 0, type = 0, size = sizeof(value); + result = RegQueryValueEx(key, valueName, 0, &type, (LPBYTE)&value, &size); + RegCloseKey(key); + + if (result != ERROR_SUCCESS || type != REG_DWORD) { + return true; + } + + return (value != 1); +} } // namespace void UnsafeOpenEmailLink(const QString &email) { @@ -277,6 +299,11 @@ void UnsafeLaunch(const QString &filepath) { } void PostprocessDownloaded(const QString &filepath) { + // Mark file saved to the NTFS file system as originating from the Internet security zone + // unless this feature is disabled by Group Policy. + if (!ShouldSaveZoneInformation()) { + return; + } auto wstringZoneFile = QDir::toNativeSeparators(filepath).toStdWString() + L":Zone.Identifier"; auto f = CreateFile(wstringZoneFile.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (f == INVALID_HANDLE_VALUE) { // :( From b962309498c243549c2b2e4c71440c4a7d88ccc1 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 3 Mar 2025 18:23:37 +0000 Subject: [PATCH 033/103] Move hints.lookup_value() out of xdg_notifications_notifications_call_notify Or it gets executed after hints.end() which clears hints --- .../platform/linux/notifications_manager_linux.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index b8c2f77b3..4cf1f82e2 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -743,6 +743,9 @@ void Manager::Private::showNotification( StartServiceAsync( _proxy.get_connection(), crl::guard(weak, [=]() mutable { + const auto hasImage = !imageKey.empty() + && hints.lookup_value(imageKey); + const auto hasBodyMarkup = HasCapability("body-markup"); const auto callbackWrap = gi::unwrap( @@ -780,9 +783,9 @@ void Manager::Private::showNotification( _interface.gobj_(), AppName.data(), 0, - (imageKey.empty() || !hints.lookup_value(imageKey) - ? base::IconName().toStdString() - : std::string()).c_str(), + (!hasImage + ? base::IconName().toStdString() + : std::string()).c_str(), (hasBodyMarkup || info.subtitle.isEmpty() ? info.title.toStdString() : info.subtitle.toStdString() From 345b2cb835f47fdae1e1ee2e34fdc4d1a111dd53 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 3 Mar 2025 18:12:36 +0000 Subject: [PATCH 034/103] Move notification closing to NotificationData destruction --- .../linux/notifications_manager_linux.cpp | 148 +++++------------- 1 file changed, 38 insertions(+), 110 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 4cf1f82e2..b1fb8327e 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -158,25 +158,21 @@ public: void clearNotification(NotificationId id); void invokeIfNotInhibited(Fn callback); - ~Private(); - private: struct NotificationData : public base::has_weak_ptr { - uint id = 0; + std::variant id; + rpl::lifetime lifetime; }; using Notification = std::unique_ptr; const not_null _manager; - - base::flat_map< - ContextId, - base::flat_map>> _notifications; - Gio::Application _application; XdgNotifications::NotificationsProxy _proxy; XdgNotifications::Notifications _interface; Media::Audio::LocalDiskCache _sounds; + base::flat_map< + ContextId, + base::flat_map> _notifications; rpl::lifetime _lifetime; }; @@ -427,7 +423,7 @@ void Manager::Private::init(XdgNotifications::NotificationsProxy proxy) { Core::Sandbox::Instance().customEnterFromEventLoop([&] { for (const auto &[key, notifications] : _notifications) { for (const auto &[msgId, notification] : notifications) { - if (id == v::get(notification)->id) { + if (id == v::get(notification->id)) { if (actionName == "default") { _manager->notificationActivated({ key, msgId }); } else if (actionName == "mail-mark-read") { @@ -451,7 +447,7 @@ void Manager::Private::init(XdgNotifications::NotificationsProxy proxy) { Core::Sandbox::Instance().customEnterFromEventLoop([&] { for (const auto &[key, notifications] : _notifications) { for (const auto &[msgId, notification] : notifications) { - if (id == v::get(notification)->id) { + if (id == v::get(notification->id)) { _manager->notificationReplied( { key, msgId }, { QString::fromStdString(text), {} }); @@ -472,7 +468,7 @@ void Manager::Private::init(XdgNotifications::NotificationsProxy proxy) { std::string token) { for (const auto &[key, notifications] : _notifications) { for (const auto &[msgId, notification] : notifications) { - if (id == v::get(notification)->id) { + if (id == v::get(notification->id)) { GLib::setenv("XDG_ACTIVATION_TOKEN", token, true); return; } @@ -505,8 +501,7 @@ void Manager::Private::init(XdgNotifications::NotificationsProxy proxy) { * In all other cases we keep the notification reference so that we may clear the notification later from history, * if the message for that notification is read (e.g. chat is opened or read from another device). */ - if (id == v::get(notification)->id - && reason == 2) { + if (id == v::get(notification->id) && reason == 2) { clearNotification({ key, msgId }); return; } @@ -535,18 +530,16 @@ void Manager::Private::showNotification( .msgId = info.itemId, }; auto notification = _application - ? std::variant( - Gio::Notification::new_( - info.subtitle.isEmpty() - ? info.title.toStdString() - : info.subtitle.toStdString() - + " (" + info.title.toStdString() + ')')) - : std::variant( - std::make_unique()); + ? Gio::Notification::new_( + info.subtitle.isEmpty() + ? info.title.toStdString() + : info.subtitle.toStdString() + + " (" + info.title.toStdString() + ')') + : Gio::Notification(); std::vector actions; auto hints = GLib::VariantDict::new_(); - v::match(notification, [&](Gio::Notification ¬ification) { + if (notification) { notification.set_body(info.message.toStdString()); notification.set_icon( @@ -601,7 +594,7 @@ void Manager::Private::showNotification( "app.notification-mark-as-read", notificationVariant); } - }, [&](const Notification ¬ification) { + } else { if (HasCapability("actions")) { actions.push_back("default"); actions.push_back(tr::lng_open_link(tr::now).toStdString()); @@ -661,11 +654,11 @@ void Manager::Private::showNotification( hints.insert_value("desktop-entry", GLib::Variant::new_string( QGuiApplication::desktopFileName().toStdString())); - }); + } const auto imageKey = GetImageKey(); if (!options.hideNameAndPhoto) { - v::match(notification, [&](Gio::Notification ¬ification) { + if (notification) { QByteArray imageData; QBuffer buffer(&imageData); buffer.open(QIODevice::WriteOnly); @@ -679,11 +672,7 @@ void Manager::Private::showNotification( reinterpret_cast(imageData.constData()), imageData.size(), [imageData] {}))); - }, [&](const Notification ¬ification) { - if (imageKey.empty()) { - return; - } - + } else if (!imageKey.empty()) { const auto image = Window::Notifications::GenerateUserpic( peer, userpicView @@ -703,43 +692,27 @@ void Manager::Private::showNotification( true, [image] {}), })); - }); - } - - auto i = _notifications.find(key); - if (i != end(_notifications)) { - auto j = i->second.find(info.itemId); - if (j != end(i->second)) { - auto oldNotification = std::move(j->second); - i->second.erase(j); - v::match(oldNotification, [&]( - const std::string &oldNotification) { - _application.withdraw_notification(oldNotification); - }, [&](const Notification &oldNotification) { - _interface.call_close_notification( - oldNotification->id, - nullptr); - }); } - } else { - i = _notifications.emplace(key).first; } - v::match(notification, [&](Gio::Notification ¬ification) { - const auto j = i->second.emplace( - info.itemId, - Gio::dbus_generate_guid()).first; - _application.send_notification( - v::get(j->second), - notification); - }, [&](Notification ¬ification) { - const auto j = i->second.emplace( - info.itemId, - std::move(notification)).first; - const auto weak = base::make_weak( - v::get(j->second).get()); + const auto &data + = _notifications[key][info.itemId] + = std::make_unique(); + data->lifetime.add([=, notification = data.get()] { + v::match(notification->id, [&](const std::string &id) { + _application.withdraw_notification(id); + }, [&](uint id) { + _interface.call_close_notification(id, nullptr); + }, [](v::null_t) {}); + }); + if (notification) { + const auto id = Gio::dbus_generate_guid(); + data->id = id; + _application.send_notification(id, notification); + } else { // work around snap's activation restriction + const auto weak = base::make_weak(data); StartServiceAsync( _proxy.get_connection(), crl::guard(weak, [=]() mutable { @@ -808,19 +781,11 @@ void Manager::Private::showNotification( &callbackWrap->wrapper, callbackWrap); })); - }); + } } void Manager::Private::clearAll() { - for (const auto &[key, notifications] : base::take(_notifications)) { - for (const auto &[msgId, notification] : notifications) { - v::match(notification, [&](const std::string ¬ification) { - _application.withdraw_notification(notification); - }, [&](const Notification ¬ification) { - _interface.call_close_notification(notification->id, nullptr); - }); - } - } + _notifications.clear(); } void Manager::Private::clearFromItem(not_null item) { @@ -837,16 +802,10 @@ void Manager::Private::clearFromItem(not_null item) { if (j == i->second.end()) { return; } - const auto taken = base::take(j->second); i->second.erase(j); if (i->second.empty()) { _notifications.erase(i); } - v::match(taken, [&](const std::string &taken) { - _application.withdraw_notification(taken); - }, [&](const Notification &taken) { - _interface.call_close_notification(taken->id, nullptr); - }); } void Manager::Private::clearFromTopic(not_null topic) { @@ -856,16 +815,7 @@ void Manager::Private::clearFromTopic(not_null topic) { }; const auto i = _notifications.find(key); if (i != _notifications.cend()) { - const auto temp = base::take(i->second); _notifications.erase(i); - - for (const auto &[msgId, notification] : temp) { - v::match(notification, [&](const std::string ¬ification) { - _application.withdraw_notification(notification); - }, [&](const Notification ¬ification) { - _interface.call_close_notification(notification->id, nullptr); - }); - } } } @@ -879,16 +829,7 @@ void Manager::Private::clearFromHistory(not_null history) { while (i != _notifications.cend() && i->first.sessionId == sessionId && i->first.peerId == peerId) { - const auto temp = base::take(i->second); i = _notifications.erase(i); - - for (const auto &[msgId, notification] : temp) { - v::match(notification, [&](const std::string ¬ification) { - _application.withdraw_notification(notification); - }, [&](const Notification ¬ification) { - _interface.call_close_notification(notification->id, nullptr); - }); - } } } @@ -898,16 +839,7 @@ void Manager::Private::clearFromSession(not_null session) { .sessionId = sessionId, }); while (i != _notifications.cend() && i->first.sessionId == sessionId) { - const auto temp = base::take(i->second); i = _notifications.erase(i); - - for (const auto &[msgId, notification] : temp) { - v::match(notification, [&](const std::string ¬ification) { - _application.withdraw_notification(notification); - }, [&](const Notification ¬ification) { - _interface.call_close_notification(notification->id, nullptr); - }); - } } } @@ -926,10 +858,6 @@ void Manager::Private::invokeIfNotInhibited(Fn callback) { } } -Manager::Private::~Private() { - clearAll(); -} - Manager::Manager(not_null system) : NativeManager(system) , _private(std::make_unique(this)) { From 4ac48d0e4a0272ec65a91791800059b0c03458a2 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 6 Mar 2025 00:33:25 +0000 Subject: [PATCH 035/103] Fix clearFromTopic on Linux Looks like it was broken since addition --- .../SourceFiles/platform/linux/notifications_manager_linux.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index b1fb8327e..6881694d9 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -811,7 +811,8 @@ void Manager::Private::clearFromItem(not_null item) { void Manager::Private::clearFromTopic(not_null topic) { const auto key = ContextId{ .sessionId = topic->session().uniqueId(), - .peerId = topic->history()->peer->id + .peerId = topic->history()->peer->id, + .topicRootId = topic->rootId(), }; const auto i = _notifications.find(key); if (i != _notifications.cend()) { From 700e10d32c118f1cc1d0a6ee00744a4b48143237 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 6 Mar 2025 13:17:05 +0000 Subject: [PATCH 036/103] Use flat_map::remove in clearFrom{Topic,Item} Now that notification closing happens in destructor, the iterator is no more needed --- .../linux/notifications_manager_linux.cpp | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 6881694d9..b41d0e4a9 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -789,35 +789,24 @@ void Manager::Private::clearAll() { } void Manager::Private::clearFromItem(not_null item) { - const auto key = ContextId{ + const auto i = _notifications.find(ContextId{ .sessionId = item->history()->session().uniqueId(), .peerId = item->history()->peer->id, .topicRootId = item->topicRootId(), - }; - const auto i = _notifications.find(key); - if (i == _notifications.cend()) { - return; - } - const auto j = i->second.find(item->id); - if (j == i->second.end()) { - return; - } - i->second.erase(j); - if (i->second.empty()) { + }); + if (i != _notifications.cend() + && i->second.remove(item->id) + && i->second.empty()) { _notifications.erase(i); } } void Manager::Private::clearFromTopic(not_null topic) { - const auto key = ContextId{ + _notifications.remove(ContextId{ .sessionId = topic->session().uniqueId(), .peerId = topic->history()->peer->id, .topicRootId = topic->rootId(), - }; - const auto i = _notifications.find(key); - if (i != _notifications.cend()) { - _notifications.erase(i); - } + }); } void Manager::Private::clearFromHistory(not_null history) { @@ -846,10 +835,10 @@ void Manager::Private::clearFromSession(not_null session) { void Manager::Private::clearNotification(NotificationId id) { auto i = _notifications.find(id.contextId); - if (i != _notifications.cend()) { - if (i->second.remove(id.msgId) && i->second.empty()) { - _notifications.erase(i); - } + if (i != _notifications.cend() + && i->second.remove(id.msgId) + && i->second.empty()) { + _notifications.erase(i); } } From abb58c58a004d1b8c453009e43c4079db1c9c59f Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 17 Feb 2025 08:30:09 +0000 Subject: [PATCH 037/103] QPlatformKeyMapper -> QKeyMapper --- .../settings/settings_shortcuts.cpp | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_shortcuts.cpp b/Telegram/SourceFiles/settings/settings_shortcuts.cpp index 82693c726..0fd7cf5fb 100644 --- a/Telegram/SourceFiles/settings/settings_shortcuts.cpp +++ b/Telegram/SourceFiles/settings/settings_shortcuts.cpp @@ -21,12 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_menu_icons.h" #include "styles/style_settings.h" -#include -#include - -#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) -#include -#endif +#include namespace Settings { namespace { @@ -388,7 +383,6 @@ struct Labeled { } const auto key = static_cast(e.get()); const auto m = key->modifiers(); - const auto integration = QGuiApplicationPrivate::platformIntegration(); const auto k = key->key(); const auto clear = !m && (k == Qt::Key_Backspace || k == Qt::Key_Delete); @@ -407,22 +401,18 @@ struct Labeled { const auto r = [&] { auto result = int(k); if (m & Qt::ShiftModifier) { + const auto keys = QKeyMapper::possibleKeys(key); + for (const auto &possible : keys) { #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) - const auto mapper = integration->keyMapper(); - const auto list = mapper->possibleKeyCombinations(key); - for (const auto &possible : list) { if (possible.keyboardModifiers() == m) { return int(possible.key()); } - } #else // Qt >= 6.7.0 - const auto keys = integration->possibleKeys(key); - for (const auto possible : keys) { if (possible > int(m)) { return possible - int(m); } - } #endif // Qt < 6.7.0 + } } return result; }(); From 909b01241b6e9c84f76359b29ecdb2c8c689f16d Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 11 Feb 2025 12:11:47 +0400 Subject: [PATCH 038/103] Update API scheme to layer 200. --- Telegram/SourceFiles/api/api_common.h | 1 + .../SourceFiles/api/api_global_privacy.cpp | 5 ++- Telegram/SourceFiles/api/api_polls.cpp | 6 +++- Telegram/SourceFiles/api/api_sending.cpp | 18 +++++++++-- Telegram/SourceFiles/apiwrap.cpp | 31 ++++++++++++++----- Telegram/SourceFiles/boxes/share_box.cpp | 8 +++-- .../export/data/export_data_types.cpp | 4 +++ .../export/data/export_data_types.h | 7 ++++- .../export/output/export_output_html.cpp | 5 +++ .../export/output/export_output_json.cpp | 4 +++ Telegram/SourceFiles/history/history_item.cpp | 10 +++++- .../media/stories/media_stories_share.cpp | 6 +++- Telegram/SourceFiles/mtproto/scheme/api.tl | 27 ++++++++++------ .../SourceFiles/window/window_peer_menu.cpp | 1 + 14 files changed, 105 insertions(+), 28 deletions(-) diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index 77c30d095..5a8347e86 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -21,6 +21,7 @@ inline constexpr auto kScheduledUntilOnlineTimestamp = TimeId(0x7FFFFFFE); struct SendOptions { uint64 price = 0; + int64 paidByStars = 0; PeerData *sendAs = nullptr; TimeId scheduled = 0; BusinessShortcutId shortcutId = 0; diff --git a/Telegram/SourceFiles/api/api_global_privacy.cpp b/Telegram/SourceFiles/api/api_global_privacy.cpp index 9e2ab1a38..fbd4a4530 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.cpp +++ b/Telegram/SourceFiles/api/api_global_privacy.cpp @@ -205,8 +205,11 @@ void GlobalPrivacy::update( | ((newRequirePremium && newRequirePremiumAllowed) ? Flag::f_new_noncontact_peers_require_premium : Flag()); + auto nonContactPaidStars = int64(0); _requestId = _api.request(MTPaccount_SetGlobalPrivacySettings( - MTP_globalPrivacySettings(MTP_flags(flags)) + MTP_globalPrivacySettings( + MTP_flags(flags), + MTP_long(nonContactPaidStars)) )).done([=](const MTPGlobalPrivacySettings &result) { _requestId = 0; apply(result); diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp index 52a5f6d6f..e1a63c853 100644 --- a/Telegram/SourceFiles/api/api_polls.cpp +++ b/Telegram/SourceFiles/api/api_polls.cpp @@ -71,6 +71,9 @@ void Polls::create( if (action.options.effectId) { sendFlags |= MTPmessages_SendMedia::Flag::f_effect; } + if (action.options.paidByStars) { + sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; + } const auto sendAs = action.options.sendAs; if (sendAs) { sendFlags |= MTPmessages_SendMedia::Flag::f_send_as; @@ -93,7 +96,8 @@ void Polls::create( MTP_int(action.options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, action.options.shortcutId), - MTP_long(action.options.effectId) + MTP_long(action.options.effectId), + MTP_long(action.options.paidByStars) ), [=](const MTPUpdates &result, const MTP::Response &response) { if (clearCloudDraft) { history->finishSavingCloudDraft( diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 80b6f1007..124ba63f9 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -111,6 +111,9 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { flags |= MessageFlag::InvertMedia; sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } + if (action.options.paidByStars) { + sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; + } auto &histories = history->owner().histories(); histories.sendPreparedMessage( @@ -129,7 +132,8 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { MTP_int(action.options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(session, action.options.shortcutId), - MTP_long(action.options.effectId) + MTP_long(action.options.effectId), + MTP_long(action.options.paidByStars) ), [=](const MTPUpdates &result, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) { api->sendMessageFail(error, peer, randomId); @@ -206,6 +210,9 @@ void SendExistingMedia( flags |= MessageFlag::InvertMedia; sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } + if (action.options.paidByStars) { + sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; + } session->data().registerMessageRandomId(randomId, newId); @@ -240,7 +247,8 @@ void SendExistingMedia( MTP_int(action.options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(session, action.options.shortcutId), - MTP_long(action.options.effectId) + MTP_long(action.options.effectId), + MTP_long(action.options.paidByStars) ), [=](const MTPUpdates &result, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) { if (error.code() == 400 @@ -380,6 +388,9 @@ bool SendDice(MessageToSend &message) { flags |= MessageFlag::InvertMedia; sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } + if (action.options.paidByStars) { + sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; + } session->data().registerMessageRandomId(randomId, newId); @@ -411,7 +422,8 @@ bool SendDice(MessageToSend &message) { MTP_int(action.options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(session, action.options.shortcutId), - MTP_long(action.options.effectId) + MTP_long(action.options.effectId), + MTP_long(action.options.paidByStars) ), [=](const MTPUpdates &result, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) { api->sendMessageFail(error, peer, randomId, newId); diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index edaea5972..a56722212 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3384,7 +3384,8 @@ void ApiWrap::forwardMessages( MTP_int(action.options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, action.options.shortcutId), - MTPint() // video_timestamp + MTPint(), // video_timestamp + MTPlong() // allow_paid_stars )).done([=](const MTPUpdates &result) { if (!scheduled) { this->updates().checkForSentToScheduled(result); @@ -3882,6 +3883,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) { sendFlags |= MTPmessages_SendMessage::Flag::f_effect; mediaFlags |= MTPmessages_SendMedia::Flag::f_effect; } + if (action.options.paidByStars) { + sendFlags |= MTPmessages_SendMessage::Flag::f_allow_paid_stars; + mediaFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; + } lastMessage = history->addNewLocalMessage({ .id = newId.msg, .flags = flags, @@ -3937,7 +3942,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) { MTP_int(action.options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), mtpShortcut, - MTP_long(action.options.effectId) + MTP_long(action.options.effectId), + MTP_long(action.options.paidByStars) ), done, fail); } else { histories.sendPreparedMessage( @@ -3955,7 +3961,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) { MTP_int(action.options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), mtpShortcut, - MTP_long(action.options.effectId) + MTP_long(action.options.effectId), + MTP_long(action.options.paidByStars) ), done, fail); } isFirst = false; @@ -4052,6 +4059,9 @@ void ApiWrap::sendInlineResult( if (action.options.hideViaBot) { sendFlags |= SendFlag::f_hide_via; } + if (action.options.paidByStars) { + sendFlags |= SendFlag::f_allow_paid_stars; + } const auto sendAs = action.options.sendAs; if (sendAs) { @@ -4089,7 +4099,8 @@ void ApiWrap::sendInlineResult( MTP_string(data->getId()), MTP_int(action.options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), - Data::ShortcutIdToMTP(_session, action.options.shortcutId) + Data::ShortcutIdToMTP(_session, action.options.shortcutId), + MTP_long(action.options.paidByStars) ), [=](const MTPUpdates &result, const MTP::Response &response) { history->finishSavingCloudDraft( topicRootId, @@ -4242,7 +4253,8 @@ void ApiWrap::sendMediaWithRandomId( | (options.sendAs ? Flag::f_send_as : Flag(0)) | (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)) | (options.effectId ? Flag::f_effect : Flag(0)) - | (options.invertCaption ? Flag::f_invert_media : Flag(0)); + | (options.invertCaption ? Flag::f_invert_media : Flag(0)) + | (options.paidByStars ? Flag::f_allow_paid_stars : Flag(0)); auto &histories = history->owner().histories(); const auto peer = history->peer; @@ -4269,7 +4281,8 @@ void ApiWrap::sendMediaWithRandomId( MTP_int(options.scheduled), (options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, options.shortcutId), - MTP_long(options.effectId) + MTP_long(options.effectId), + MTP_long(options.paidByStars) ), [=](const MTPUpdates &result, const MTP::Response &response) { if (done) done(true); if (updateRecentStickers) { @@ -4317,7 +4330,8 @@ void ApiWrap::sendMultiPaidMedia( | (options.sendAs ? Flag::f_send_as : Flag(0)) | (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)) | (options.effectId ? Flag::f_effect : Flag(0)) - | (options.invertCaption ? Flag::f_invert_media : Flag(0)); + | (options.invertCaption ? Flag::f_invert_media : Flag(0)) + | (options.paidByStars ? Flag::f_allow_paid_stars : Flag(0)); auto &histories = history->owner().histories(); const auto peer = history->peer; @@ -4343,7 +4357,8 @@ void ApiWrap::sendMultiPaidMedia( MTP_int(options.scheduled), (options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, options.shortcutId), - MTP_long(options.effectId) + MTP_long(options.effectId), + MTP_long(options.paidByStars) ), [=](const MTPUpdates &result, const MTP::Response &response) { if (const auto album = _sendingAlbums.take(groupId)) { const auto copy = (*album)->items; diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 437d7591b..fb6822f5d 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -1582,7 +1582,10 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( : Flag(0)) | (options.shortcutId ? Flag::f_quick_reply_shortcut - : Flag(0)); + : Flag(0)) + | (options.paidByStars + ? Flag::f_allow_paid_stars + : Flag()); threadHistory->sendRequestId = api.request( MTPmessages_ForwardMessages( MTP_flags(sendFlags), @@ -1594,7 +1597,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( MTP_int(options.scheduled), MTP_inputPeerEmpty(), // send_as Data::ShortcutIdToMTP(session, options.shortcutId), - MTP_int(videoTimestamp.value_or(0)) + MTP_int(videoTimestamp.value_or(0)), + MTP_long(options.paidByStars) )).done([=](const MTPUpdates &updates, mtpRequestId reqId) { threadHistory->session().api().applyUpdates(updates); state->requests.remove(reqId); diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 6e1ca060b..cb85d0b59 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1704,6 +1704,10 @@ ServiceAction ParseServiceAction( .giftId = uint64(gift.vid().v), }; }); + }, [&](const MTPDmessageActionPaidMessage &data) { + result.content = ActionPaidMessage{ + .stars = int64(data.vstars().v), + }; }, [](const MTPDmessageActionEmpty &data) {}); return result; } diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index d460ee196..e1fe647a3 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -662,6 +662,10 @@ struct ActionStarGift { bool limited = false; }; +struct ActionPaidMessage { + int64 stars = 0; +}; + struct ServiceAction { std::variant< v::null_t, @@ -707,7 +711,8 @@ struct ServiceAction { ActionPaymentRefunded, ActionGiftStars, ActionPrizeStars, - ActionStarGift> content; + ActionStarGift, + ActionPaidMessage> content; }; ServiceAction ParseServiceAction( diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index eda916d0a..2f996638b 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -1367,6 +1367,11 @@ auto HtmlWriter::Wrap::pushMessage( + " sent you a gift of " + QByteArray::number(data.stars) + " Telegram Stars."; + }, [&](const ActionPaidMessage &data) { + return serviceFrom + + " paid for the message " + + QString::number(data.stars).toUtf8() + + " Telegram Stars."; }, [](v::null_t) { return QByteArray(); }); if (!serviceText.isEmpty()) { diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index dee8d1e94..f53573911 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -663,6 +663,10 @@ QByteArray SerializeMessage( push("is_limited", data.limited); push("is_anonymous", data.anonymous); pushBare("gift_text", SerializeText(context, data.text)); + }, [&](const ActionPaidMessage &data) { + pushActor(); + pushAction("send_paid_message"); + push("stars", data.stars); }, [](v::null_t) {}); if (v::is_null(message.action.content)) { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 8bd1c4f72..cd2e5b724 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -5574,6 +5574,13 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return result; }; + auto preparePaidMessage = [&]( + const MTPDmessageActionPaidMessage &action) { + auto result = PreparedServiceText(); + result.text.text = u"paid for message"_q; AssertIsDebug(); + return result; + }; + setServiceText(action.match( prepareChatAddUserText, prepareChatJoinedByLink, @@ -5622,7 +5629,8 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { prepareStarGift, prepareStarGiftUnique, PrepareEmptyText, - PrepareErrorText)); + PrepareErrorText, + preparePaidMessage)); // Additional information. applyAction(action); diff --git a/Telegram/SourceFiles/media/stories/media_stories_share.cpp b/Telegram/SourceFiles/media/stories/media_stories_share.cpp index 67b1dc925..e9b4cd36a 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_share.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_share.cpp @@ -131,6 +131,9 @@ namespace Media::Stories { if (options.invertCaption) { sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } + if (options.paidByStars) { + sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; + } const auto done = [=] { if (!--state->requests) { if (show->valid()) { @@ -155,7 +158,8 @@ namespace Media::Stories { MTP_int(options.scheduled), MTP_inputPeerEmpty(), Data::ShortcutIdToMTP(session, options.shortcutId), - MTP_long(options.effectId) + MTP_long(options.effectId), + MTP_long(options.paidByStars) ), [=]( const MTPUpdates &result, const MTP::Response &response) { diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index b2d27fd06..568d449f4 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -103,7 +103,7 @@ channel#e00998b7 flags:# creator:flags.0?true left:flags.2?true broadcast:flags. channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#2633421b flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions reactions_limit:flags.20?int = ChatFull; -channelFull#52d6806b flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int = ChatFull; +channelFull#67d2e29d flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int send_paid_messages_stars:flags2.20?long = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -185,6 +185,7 @@ messageActionGiftStars#45d5b021 flags:# currency:string amount:long stars:long c messageActionPrizeStars#b00c47a2 flags:# unclaimed:flags.0?true stars:long transaction_id:string boost_peer:Peer giveaway_msg_id:int = MessageAction; messageActionStarGift#4717e8a4 flags:# name_hidden:flags.0?true saved:flags.2?true converted:flags.3?true upgraded:flags.5?true refunded:flags.9?true can_upgrade:flags.10?true gift:StarGift message:flags.1?TextWithEntities convert_stars:flags.4?long upgrade_msg_id:flags.5?int upgrade_stars:flags.8?long from_id:flags.11?Peer peer:flags.12?Peer saved_id:flags.12?long = MessageAction; messageActionStarGiftUnique#acdfcb81 flags:# upgrade:flags.0?true transferred:flags.1?true saved:flags.2?true refunded:flags.5?true gift:StarGift can_export_at:flags.3?int transfer_stars:flags.4?long from_id:flags.6?Peer peer:flags.7?Peer saved_id:flags.7?long = MessageAction; +messageActionPaidMessage#5cd2501f stars:long = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -220,7 +221,7 @@ inputPeerNotifySettings#cacb6ae2 flags:# show_previews:flags.0?Bool silent:flags peerNotifySettings#99622c0c flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int ios_sound:flags.3?NotificationSound android_sound:flags.4?NotificationSound other_sound:flags.5?NotificationSound stories_muted:flags.6?Bool stories_hide_sender:flags.7?Bool stories_ios_sound:flags.8?NotificationSound stories_android_sound:flags.9?NotificationSound stories_other_sound:flags.10?NotificationSound = PeerNotifySettings; -peerSettings#acd66c5e flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true business_bot_paused:flags.11?true business_bot_can_reply:flags.12?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int business_bot_id:flags.13?long business_bot_manage_url:flags.13?string = PeerSettings; +peerSettings#a8639d72 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true business_bot_paused:flags.11?true business_bot_can_reply:flags.12?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int business_bot_id:flags.13?long business_bot_manage_url:flags.13?string charge_paid_message_stars:flags.14?long = PeerSettings; wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper; wallPaperNoFile#e0804116 id:long flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper; @@ -236,7 +237,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason; inputReportReasonIllegalDrugs#a8eb2be = ReportReason; inputReportReasonPersonalDetails#9ec7863d = ReportReason; -userFull#4d975bbc flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# sponsored_enabled:flags2.7?true can_view_revenue:flags2.9?true bot_can_manage_emoji_status:flags2.10?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage business_intro:flags2.4?BusinessIntro birthday:flags2.5?Birthday personal_channel_id:flags2.6?long personal_channel_message:flags2.6?int stargifts_count:flags2.8?int starref_program:flags2.11?StarRefProgram bot_verification:flags2.12?BotVerification = UserFull; +userFull#8555f3c2 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# sponsored_enabled:flags2.7?true can_view_revenue:flags2.9?true bot_can_manage_emoji_status:flags2.10?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage business_intro:flags2.4?BusinessIntro birthday:flags2.5?Birthday personal_channel_id:flags2.6?long personal_channel_message:flags2.6?int stargifts_count:flags2.8?int starref_program:flags2.11?StarRefProgram bot_verification:flags2.12?BotVerification send_paid_messages_stars:flags2.14?long = UserFull; contact#145ade0b user_id:long mutual:Bool = Contact; @@ -547,6 +548,7 @@ privacyKeyVoiceMessages#697f414 = PrivacyKey; privacyKeyAbout#a486b761 = PrivacyKey; privacyKeyBirthday#2000a518 = PrivacyKey; privacyKeyStarGiftsAutoSave#2ca4fdf8 = PrivacyKey; +privacyKeyNoPaidMessages#17d348d2 = PrivacyKey; inputPrivacyValueAllowContacts#d09e07b = InputPrivacyRule; inputPrivacyValueAllowAll#184b35ce = InputPrivacyRule; @@ -1309,7 +1311,7 @@ statsGroupTopInviter#535f779d user_id:long invitations:int = StatsGroupTopInvite stats.megagroupStats#ef7ff916 period:StatsDateRangeDays members:StatsAbsValueAndPrev messages:StatsAbsValueAndPrev viewers:StatsAbsValueAndPrev posters:StatsAbsValueAndPrev growth_graph:StatsGraph members_graph:StatsGraph new_members_by_source_graph:StatsGraph languages_graph:StatsGraph messages_graph:StatsGraph actions_graph:StatsGraph top_hours_graph:StatsGraph weekdays_graph:StatsGraph top_posters:Vector top_admins:Vector top_inviters:Vector users:Vector = stats.MegagroupStats; -globalPrivacySettings#734c4ccb flags:# archive_and_mute_new_noncontact_peers:flags.0?true keep_archived_unmuted:flags.1?true keep_archived_folders:flags.2?true hide_read_marks:flags.3?true new_noncontact_peers_require_premium:flags.4?true = GlobalPrivacySettings; +globalPrivacySettings#c9d8df1c flags:# archive_and_mute_new_noncontact_peers:flags.0?true keep_archived_unmuted:flags.1?true keep_archived_folders:flags.2?true hide_read_marks:flags.3?true new_noncontact_peers_require_premium:flags.4?true noncontact_peers_paid_stars:flags.5?long = GlobalPrivacySettings; help.countryCode#4203c5ef flags:# country_code:string prefixes:flags.0?Vector patterns:flags.1?Vector = help.CountryCode; @@ -1838,7 +1840,7 @@ starsTransactionPeerAPI#f9677aad = StarsTransactionPeer; starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; -starsTransaction#64dfc926 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true id:string stars:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount = StarsTransaction; +starsTransaction#64dfc926 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true paid_message:flags.19?true id:string stars:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount = StarsTransaction; payments.starsStatus#6c9ce8ed flags:# balance:StarsAmount subscriptions:flags.1?Vector subscriptions_next_offset:flags.2?string subscriptions_missing_balance:flags.4?long history:flags.3?Vector next_offset:flags.0?string chats:Vector users:Vector = payments.StarsStatus; @@ -1938,6 +1940,8 @@ paidReactionPrivacyDefault#206ad49e = PaidReactionPrivacy; paidReactionPrivacyAnonymous#1f0c1ad9 = PaidReactionPrivacy; paidReactionPrivacyPeer#dc6cfcf0 peer:InputPeer = PaidReactionPrivacy; +account.paidMessagesRevenue#1e109708 stars_amount:long = account.PaidMessagesRevenue; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -2089,6 +2093,8 @@ account.toggleSponsoredMessages#b9d9a38d enabled:Bool = Bool; account.getReactionsNotifySettings#6dd654c = ReactionsNotifySettings; account.setReactionsNotifySettings#316ce548 settings:ReactionsNotifySettings = ReactionsNotifySettings; account.getCollectibleEmojiStatuses#2e7b4543 hash:long = account.EmojiStatuses; +account.addNoPaidMessagesException#6f688aa7 flags:# refund_charged:flags.0?true user_id:InputUser = Bool; +account.getPaidMessagesRevenue#f1266f38 user_id:InputUser = account.PaidMessagesRevenue; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#b60f5918 id:InputUser = users.UserFull; @@ -2131,9 +2137,9 @@ messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?t messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; -messages.sendMessage#983f9745 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long = Updates; -messages.sendMedia#7852834e flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long = Updates; -messages.forwardMessages#6d74da08 flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true allow_paid_floodskip:flags.19?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer top_msg_id:flags.9?int schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut video_timestamp:flags.20?int = Updates; +messages.sendMessage#fbf2340a flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long allow_paid_stars:flags.21?long = Updates; +messages.sendMedia#a550cd78 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long allow_paid_stars:flags.21?long = Updates; +messages.forwardMessages#bb9fa475 flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true allow_paid_floodskip:flags.19?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer top_msg_id:flags.9?int schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut video_timestamp:flags.20?int allow_paid_stars:flags.21?long = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings; messages.report#fc78af9b peer:InputPeer id:Vector option:bytes message:string = ReportResult; @@ -2176,7 +2182,7 @@ messages.getSavedGifs#5cf09635 hash:long = messages.SavedGifs; messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults; messages.setInlineBotResults#bb12a419 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM switch_webview:flags.4?InlineBotWebView = Bool; -messages.sendInlineBotResult#3ebee86a flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to:flags.0?InputReplyTo random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut = Updates; +messages.sendInlineBotResult#c0cf7646 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to:flags.0?InputReplyTo random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut allow_paid_stars:flags.21?long = Updates; messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; messages.editMessage#dfd14005 flags:# no_webpage:flags.1?true invert_media:flags.16?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.15?int quick_reply_shortcut_id:flags.17?int = Updates; messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true invert_media:flags.16?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool; @@ -2454,6 +2460,7 @@ channels.setBoostsToUnblockRestrictions#ad399cee channel:InputChannel boosts:int channels.setEmojiStickers#3cd930b7 channel:InputChannel stickerset:InputStickerSet = Bool; channels.restrictSponsoredMessages#9ae91519 channel:InputChannel restricted:Bool = Updates; channels.searchPosts#d19f987b hashtag:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; +channels.updatePaidMessagesPrice#fc84653f channel:InputChannel send_paid_messages_stars:long = Updates; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -2653,4 +2660,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo; -// LAYER 199 +// LAYER 200 diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 036093e55..f5ea985ae 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -144,6 +144,7 @@ void ShareBotGame( MTP_int(0), // schedule_date MTPInputPeer(), // send_as MTPInputQuickReplyShortcut(), + MTPlong(), MTPlong() ), [=](const MTPUpdates &, const MTP::Response &) { }, [=](const MTP::Error &error, const MTP::Response &) { From f2aa3afbbb698956e6e84492ece3b4c30a31a2bc Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 11 Feb 2025 19:26:20 +0400 Subject: [PATCH 039/103] Allow editing charge-for-message privacy. --- Telegram/Resources/langs/lang.strings | 34 +++ .../SourceFiles/api/api_global_privacy.cpp | 75 ++++-- Telegram/SourceFiles/api/api_global_privacy.h | 12 +- .../SourceFiles/boxes/edit_privacy_box.cpp | 232 +++++++++++++++++- Telegram/SourceFiles/data/data_user.cpp | 1 - .../peer_gifts/info_peer_gifts_widget.cpp | 2 +- Telegram/SourceFiles/mtproto/scheme/api.tl | 1 + .../settings/settings_privacy_security.cpp | 12 +- 8 files changed, 327 insertions(+), 42 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b8025092f..be5e8e8b6 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1218,6 +1218,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_edit_privacy_contacts" = "My contacts"; "lng_edit_privacy_close_friends" = "Close friends"; "lng_edit_privacy_contacts_and_premium" = "Contacts & Premium"; +"lng_edit_privacy_paid" = "Paid"; "lng_edit_privacy_contacts_and_miniapps" = "Contacts & Mini Apps"; "lng_edit_privacy_nobody" = "Nobody"; "lng_edit_privacy_premium" = "Premium users"; @@ -1356,6 +1357,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_messages_privacy_premium_about" = "Subscribe now to change this setting and get access to other exclusive features of Telegram Premium."; "lng_messages_privacy_premium" = "Only subscribers of {link} can select this option."; "lng_messages_privacy_premium_link" = "Telegram Premium"; +"lng_messages_privacy_charge" = "Charge for messages"; +"lng_messages_privacy_charge_about" = "Charge a fee for messages from people outside your contacts or those you haven't messaged first."; +"lng_messages_privacy_price" = "Set your price per message"; +"lng_messages_privacy_price_about" = "You will receive {percent} of the selected fee ({amount}) for each incoming message."; +"lng_messages_privacy_exceptions" = "Exceptions"; +"lng_messages_privacy_remove_fee" = "Remove Fee"; +"lng_messages_privacy_remove_about" = "Add users or entire groups who won't be charged for sending messages to you."; "lng_self_destruct_title" = "Account self-destruction"; "lng_self_destruct_description" = "If you don't come online at least once within this period, your account will be deleted along with all groups, messages and contacts."; @@ -2158,6 +2166,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_boost_apply#other" = "{from} boosted the group {count} times"; "lng_action_set_chat_intro" = "{from} added the message below for all empty chats. How?"; "lng_action_payment_refunded" = "{peer} refunded {amount}"; +"lng_action_paid_message_sent#one" = "You paid {count} Star to send a message"; +"lng_action_paid_message_sent#other" = "You paid {count} Stars to send a message"; +"lng_action_paid_message_got#one" = "You received {count} Star from {name}"; +"lng_action_paid_message_got#other" = "You received {count} Stars from {name}"; "lng_similar_channels_title" = "Similar channels"; "lng_similar_channels_view_all" = "View all"; @@ -4798,6 +4810,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_rights_boosts_no_restrict" = "Do not restrict boosters"; "lng_rights_boosts_about" = "Turn this on to always allow users who boosted your group to send messages and media."; "lng_rights_boosts_about_on" = "Choose how many boosts a user must give to the group to bypass restrictions on sending messages."; +"lng_rights_charge_stars" = "Charge Stars for Messages"; +"lng_rights_charge_stars_about" = "If you turn this on, regular members of the group will have to pay Stars to send messages."; +"lng_rights_charge_price" = "Set price per message"; +"lng_rights_charge_price_about" = "Your group will receive {percent} of the selected fee ({amount}) for each incoming message."; "lng_slowmode_enabled" = "Slow Mode is active.\nYou can send your next message in {left}."; "lng_slowmode_no_many" = "Slow mode is enabled. You can't send more than one message at a time."; @@ -4805,6 +4821,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_slowmode_seconds#one" = "{count} second"; "lng_slowmode_seconds#other" = "{count} seconds"; +"lng_payment_for_message#one" = "Pay {star}{count} for 1 Message"; +"lng_payment_for_message#other" = "Pay {star}{count} for 1 Message"; +"lng_payment_confirm_title" = "Confirm payment"; +"lng_payment_confirm_text#one" = "{name} charges **{count}** Star per message."; +"lng_payment_confirm_text#other" = "{name} charges **{count}** Stars per message."; +"lng_payment_confirm_sure#one" = "Would you like to pay **{count}** Star to send one message?"; +"lng_payment_confirm_sure#other" = "Would you like to pay **{count}** Stars to send one message?"; +"lng_payment_confirm_dont_ask" = "Don't ask me again"; +"lng_payment_confirm_button" = "Pay for 1 Message"; +"lng_payment_bar_text#one" = "{name} must pay {star}{count} for each message to you."; +"lng_payment_bar_text#other" = "{name} must pay {star}{count} for each message to you."; +"lng_payment_bar_button" = "Remove Fee"; +"lng_payment_refund_title" = "Remove Fee"; +"lng_payment_refund_text" = "Are you sure you want to allow {name} to message you for free?"; +"lng_payment_refund_also#one" = "Refund already paid {count} Star"; +"lng_payment_refund_also#other" = "Refund already paid {count} Stars"; +"lng_payment_refund_confirm" = "Confirm"; + "lng_rights_gigagroup_title" = "Broadcast group"; "lng_rights_gigagroup_convert" = "Convert to Broadcast Group"; "lng_rights_gigagroup_about" = "Broadcast groups can have over 200,000 members, but only admins can send messages in them."; diff --git a/Telegram/SourceFiles/api/api_global_privacy.cpp b/Telegram/SourceFiles/api/api_global_privacy.cpp index fbd4a4530..90c56ae4e 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.cpp +++ b/Telegram/SourceFiles/api/api_global_privacy.cpp @@ -114,7 +114,8 @@ void GlobalPrivacy::updateHideReadTime(bool hide) { archiveAndMuteCurrent(), unarchiveOnNewMessageCurrent(), hide, - newRequirePremiumCurrent()); + newRequirePremiumCurrent(), + newChargeStarsCurrent()); } bool GlobalPrivacy::hideReadTimeCurrent() const { @@ -125,14 +126,6 @@ rpl::producer GlobalPrivacy::hideReadTime() const { return _hideReadTime.value(); } -void GlobalPrivacy::updateNewRequirePremium(bool value) { - update( - archiveAndMuteCurrent(), - unarchiveOnNewMessageCurrent(), - hideReadTimeCurrent(), - value); -} - bool GlobalPrivacy::newRequirePremiumCurrent() const { return _newRequirePremium.current(); } @@ -141,6 +134,25 @@ rpl::producer GlobalPrivacy::newRequirePremium() const { return _newRequirePremium.value(); } +int GlobalPrivacy::newChargeStarsCurrent() const { + return _newChargeStars.current(); +} + +rpl::producer GlobalPrivacy::newChargeStars() const { + return _newChargeStars.value(); +} + +void GlobalPrivacy::updateMessagesPrivacy( + bool requirePremium, + int chargeStars) { + update( + archiveAndMuteCurrent(), + unarchiveOnNewMessageCurrent(), + hideReadTimeCurrent(), + requirePremium, + chargeStars); +} + void GlobalPrivacy::loadPaidReactionShownPeer() { if (_paidReactionShownPeerLoaded) { return; @@ -169,7 +181,8 @@ void GlobalPrivacy::updateArchiveAndMute(bool value) { value, unarchiveOnNewMessageCurrent(), hideReadTimeCurrent(), - newRequirePremiumCurrent()); + newRequirePremiumCurrent(), + newChargeStarsCurrent()); } void GlobalPrivacy::updateUnarchiveOnNewMessage( @@ -178,14 +191,16 @@ void GlobalPrivacy::updateUnarchiveOnNewMessage( archiveAndMuteCurrent(), value, hideReadTimeCurrent(), - newRequirePremiumCurrent()); + newRequirePremiumCurrent(), + newChargeStarsCurrent()); } void GlobalPrivacy::update( bool archiveAndMute, UnarchiveOnNewMessage unarchiveOnNewMessage, bool hideReadTime, - bool newRequirePremium) { + bool newRequirePremium, + int newChargeStars) { using Flag = MTPDglobalPrivacySettings::Flag; _api.request(_requestId).cancel(); @@ -204,38 +219,44 @@ void GlobalPrivacy::update( | (hideReadTime ? Flag::f_hide_read_marks : Flag()) | ((newRequirePremium && newRequirePremiumAllowed) ? Flag::f_new_noncontact_peers_require_premium - : Flag()); - auto nonContactPaidStars = int64(0); + : Flag()) + | Flag::f_noncontact_peers_paid_stars; _requestId = _api.request(MTPaccount_SetGlobalPrivacySettings( MTP_globalPrivacySettings( MTP_flags(flags), - MTP_long(nonContactPaidStars)) + MTP_long(newChargeStars)) )).done([=](const MTPGlobalPrivacySettings &result) { _requestId = 0; apply(result); }).fail([=](const MTP::Error &error) { _requestId = 0; if (error.type() == u"PREMIUM_ACCOUNT_REQUIRED"_q) { - update(archiveAndMute, unarchiveOnNewMessage, hideReadTime, {}); + update( + archiveAndMute, + unarchiveOnNewMessage, + hideReadTime, + false, + 0); } }).send(); _archiveAndMute = archiveAndMute; _unarchiveOnNewMessage = unarchiveOnNewMessage; _hideReadTime = hideReadTime; _newRequirePremium = newRequirePremium; + _newChargeStars = newChargeStars; } -void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) { - data.match([&](const MTPDglobalPrivacySettings &data) { - _archiveAndMute = data.is_archive_and_mute_new_noncontact_peers(); - _unarchiveOnNewMessage = data.is_keep_archived_unmuted() - ? UnarchiveOnNewMessage::None - : data.is_keep_archived_folders() - ? UnarchiveOnNewMessage::NotInFoldersUnmuted - : UnarchiveOnNewMessage::AnyUnmuted; - _hideReadTime = data.is_hide_read_marks(); - _newRequirePremium = data.is_new_noncontact_peers_require_premium(); - }); +void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &settings) { + const auto &data = settings.data(); + _archiveAndMute = data.is_archive_and_mute_new_noncontact_peers(); + _unarchiveOnNewMessage = data.is_keep_archived_unmuted() + ? UnarchiveOnNewMessage::None + : data.is_keep_archived_folders() + ? UnarchiveOnNewMessage::NotInFoldersUnmuted + : UnarchiveOnNewMessage::AnyUnmuted; + _hideReadTime = data.is_hide_read_marks(); + _newRequirePremium = data.is_new_noncontact_peers_require_premium(); + _newChargeStars = data.vnoncontact_peers_paid_stars().value_or_empty(); } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_global_privacy.h b/Telegram/SourceFiles/api/api_global_privacy.h index 6c5848961..6b0fc064b 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.h +++ b/Telegram/SourceFiles/api/api_global_privacy.h @@ -49,23 +49,28 @@ public: [[nodiscard]] bool hideReadTimeCurrent() const; [[nodiscard]] rpl::producer hideReadTime() const; - void updateNewRequirePremium(bool value); [[nodiscard]] bool newRequirePremiumCurrent() const; [[nodiscard]] rpl::producer newRequirePremium() const; + [[nodiscard]] int newChargeStarsCurrent() const; + [[nodiscard]] rpl::producer newChargeStars() const; + + void updateMessagesPrivacy(bool requirePremium, int chargeStars); + void loadPaidReactionShownPeer(); void updatePaidReactionShownPeer(PeerId shownPeer); [[nodiscard]] PeerId paidReactionShownPeerCurrent() const; [[nodiscard]] rpl::producer paidReactionShownPeer() const; private: - void apply(const MTPGlobalPrivacySettings &data); + void apply(const MTPGlobalPrivacySettings &settings); void update( bool archiveAndMute, UnarchiveOnNewMessage unarchiveOnNewMessage, bool hideReadTime, - bool newRequirePremium); + bool newRequirePremium, + int newChargeStars); const not_null _session; MTP::Sender _api; @@ -76,6 +81,7 @@ private: rpl::variable _showArchiveAndMute = false; rpl::variable _hideReadTime = false; rpl::variable _newRequirePremium = false; + rpl::variable _newChargeStars = 0; rpl::variable _paidReactionShownPeer = false; std::vector> _callbacks; bool _paidReactionShownPeerLoaded = false; diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp index 94c4af054..818ed5c35 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp @@ -12,7 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/premium_graphics.h" #include "ui/layers/generic_box.h" #include "ui/widgets/checkbox.h" +#include "ui/widgets/continuous_sliders.h" #include "ui/widgets/shadow.h" +#include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/wrap/slide_wrap.h" @@ -42,6 +44,10 @@ namespace { constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value; constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value; +constexpr auto kGetPercent = 85; +constexpr auto kStarsMin = 10; +constexpr auto kStarsMax = 9000; +constexpr auto kDefaultChargeStars = 200; using Exceptions = Api::UserPrivacy::Exceptions; @@ -452,6 +458,109 @@ auto PrivacyExceptionsBoxController::createRow(not_null history) return result; } +[[nodiscard]] object_ptr MakeChargeStarsSlider( + QWidget *parent, + not_null sliderStyle, + not_null labelStyle, + int valuesCount, + Fn valueByIndex, + int value, + Fn valueProgress, + Fn valueFinished) { + auto result = object_ptr(parent); + const auto raw = result.data(); + + const auto labels = raw->add(object_ptr(raw)); + const auto min = Ui::CreateChild( + raw, + QString::number(kStarsMin), + *labelStyle); + const auto max = Ui::CreateChild( + raw, + QString::number(kStarsMax), + *labelStyle); + const auto current = Ui::CreateChild( + raw, + QString::number(value), + *labelStyle); + min->setTextColorOverride(st::windowSubTextFg->c); + max->setTextColorOverride(st::windowSubTextFg->c); + const auto slider = raw->add(object_ptr( + raw, + *sliderStyle)); + labels->resize( + labels->width(), + current->height() + st::defaultVerticalListSkip); + struct State { + int indexMin = 0; + int index = 0; + }; + const auto state = raw->lifetime().make_state(); + const auto updateByIndex = [=] { + const auto outer = labels->width(); + const auto minWidth = min->width(); + const auto maxWidth = max->width(); + const auto currentWidth = current->width(); + if (minWidth + maxWidth + currentWidth > outer) { + return; + } + + min->moveToLeft(0, 0, outer); + max->moveToRight(0, 0, outer); + current->moveToLeft((outer - current->width()) / 2, 0, outer); + }; + const auto updateByValue = [=](int value) { + current->setText( + tr::lng_action_gift_for_stars(tr::now, lt_count, value)); + + state->index = 0; + auto maxIndex = valuesCount - 1; + while (state->index < maxIndex) { + const auto mid = (state->index + maxIndex) / 2; + const auto midValue = valueByIndex(mid); + if (midValue == value) { + state->index = mid; + break; + } else if (midValue < value) { + state->index = mid + 1; + } else { + maxIndex = mid - 1; + } + } + updateByIndex(); + }; + const auto progress = [=](int value) { + updateByValue(value); + valueProgress(value); + }; + const auto finished = [=](int value) { + updateByValue(value); + valueFinished(value); + }; + style::PaletteChanged() | rpl::start_with_next([=] { + min->setTextColorOverride(st::windowSubTextFg->c); + max->setTextColorOverride(st::windowSubTextFg->c); + }, raw->lifetime()); + updateByValue(value); + state->indexMin = 0; + + slider->setPseudoDiscrete( + valuesCount, + valueByIndex, + value, + progress, + finished, + state->indexMin); + slider->resize(slider->width(), sliderStyle->seekSize.height()); + + raw->widthValue() | rpl::start_with_next([=](int width) { + labels->resizeToWidth(width); + updateByIndex(); + }, slider->lifetime()); + + return result; +} + } // namespace bool EditPrivacyController::hasOption(Option option) const { @@ -812,6 +921,7 @@ void EditMessagesPrivacyBox( constexpr auto kOptionAll = 0; constexpr auto kOptionPremium = 1; + constexpr auto kOptionCharge = 2; const auto allowed = [=] { return controller->session().premium() @@ -824,7 +934,11 @@ void EditMessagesPrivacyBox( Ui::AddSkip(inner, st::messagePrivacyTopSkip); Ui::AddSubsectionTitle(inner, tr::lng_messages_privacy_subtitle()); const auto group = std::make_shared( - privacy->newRequirePremiumCurrent() ? kOptionPremium : kOptionAll); + (privacy->newRequirePremiumCurrent() + ? kOptionPremium + : privacy->newChargeStarsCurrent() + ? kOptionCharge + : kOptionAll)); inner->add( object_ptr( inner, @@ -846,6 +960,107 @@ void EditMessagesPrivacyBox( 0, st::messagePrivacyBottomSkip)); + Ui::AddDividerText(inner, tr::lng_messages_privacy_about()); + + const auto charged = inner->add( + object_ptr( + inner, + group, + kOptionCharge, + tr::lng_messages_privacy_charge(tr::now), + st::messagePrivacyCheck), + st::settingsSendTypePadding + style::margins( + 0, + st::messagePrivacyBottomSkip, + 0, + st::messagePrivacyBottomSkip)); + + Ui::AddDividerText(inner, tr::lng_messages_privacy_charge_about()); + + const auto chargeWrap = inner->add( + object_ptr>( + inner, + object_ptr(inner))); + const auto chargeInner = chargeWrap->entity(); + + Ui::AddSkip(chargeInner); + Ui::AddSubsectionTitle(chargeInner, tr::lng_messages_privacy_price()); + + struct State { + rpl::variable stars; + }; + const auto state = std::make_shared(); + const auto savedValue = privacy->newChargeStarsCurrent(); + const auto chargeStars = savedValue ? savedValue : kDefaultChargeStars; + state->stars = chargeStars; + + auto values = std::vector(); + if (chargeStars < kStarsMin) { + values.push_back(chargeStars); + } + for (auto i = kStarsMin; i < 100; ++i) { + values.push_back(i); + } + for (auto i = 100; i < 1000; i += 10) { + if (i < chargeStars + 10 && chargeStars < i) { + values.push_back(chargeStars); + } + values.push_back(i); + } + for (auto i = 1000; i < kStarsMax + 1; i += 100) { + if (i < chargeStars + 100 && chargeStars < i) { + values.push_back(chargeStars); + } + values.push_back(i); + } + const auto valuesCount = int(values.size()); + const auto setStars = [=](int value) { + state->stars = value; + }; + chargeInner->add( + MakeChargeStarsSlider( + chargeInner, + &st::settingsScale, + &st::settingsScaleLabel, + valuesCount, + [=](int index) { return values[index]; }, + chargeStars, + setStars, + setStars), + st::boxRowPadding); + Ui::AddSkip(chargeInner); + + auto dollars = state->stars.value() | rpl::map([=](int stars) { + const auto ratio = controller->session().appConfig().get( + u"stars_usd_withdraw_rate_x1000"_q, + 1200); + const auto dollars = int(base::SafeRound(stars * (ratio / 1000.))); + return '~' + Ui::FillAmountAndCurrency(dollars, u"USD"_q); + }); + Ui::AddDividerText( + chargeInner, + tr::lng_messages_privacy_price_about( + lt_percent, + rpl::single(QString::number(kGetPercent) + '%'), + lt_amount, + std::move(dollars))); + + Ui::AddSkip(chargeInner); + Ui::AddSubsectionTitle( + chargeInner, + tr::lng_messages_privacy_exceptions()); + const auto exceptions = Settings::AddButtonWithLabel( + chargeInner, + tr::lng_messages_privacy_remove_fee(), + rpl::single(u""_q), + st::settingsButtonNoIcon); + Ui::AddSkip(chargeInner); + Ui::AddDividerText(chargeInner, tr::lng_messages_privacy_remove_about()); + + using namespace rpl::mappers; + chargeWrap->toggleOn(group->value() | rpl::map(_1 == kOptionCharge)); + chargeWrap->finishAnimating(); + using WeakToast = base::weak_ptr; const auto toast = std::make_shared(); const auto showToast = [=] { @@ -875,19 +1090,18 @@ void EditMessagesPrivacyBox( }), }); }; + if (!allowed()) { CreateRadiobuttonLock(restricted, st::messagePrivacyCheck); + CreateRadiobuttonLock(charged, st::messagePrivacyCheck); group->setChangedCallback([=](int value) { - if (value == kOptionPremium) { + if (value == kOptionPremium || value == kOptionCharge) { group->setValue(kOptionAll); showToast(); } }); - } - Ui::AddDividerText(inner, tr::lng_messages_privacy_about()); - if (!allowed()) { Ui::AddSkip(inner); Settings::AddButtonWithIcon( inner, @@ -907,8 +1121,12 @@ void EditMessagesPrivacyBox( } else { box->addButton(tr::lng_settings_save(), [=] { if (allowed()) { - privacy->updateNewRequirePremium( - group->current() == kOptionPremium); + const auto value = group->current(); + const auto premiumRequired = (value == kOptionPremium); + const auto chargeStars = (value == kOptionCharge) + ? state->stars.current() + : 0; + privacy->updateMessagesPrivacy(premiumRequired, chargeStars); box->closeBox(); } else { showToast(); diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 7b53de283..0c36d9945 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -610,7 +610,6 @@ void UserData::setCallsStatus(CallsStatus callsStatus) { } } - Data::Birthday UserData::birthday() const { return _birthday; } diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp index 9656bd08a..898125cf3 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp @@ -630,7 +630,7 @@ void Widget::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) { }); }, filter.skipUnique ? nullptr : &st::mediaPlayerMenuCheck); - if (_inner->peer()->canManageGifts() && _inner->peer()->isChannel()) { + if (_inner->peer()->canManageGifts()) { addAction({ .isSeparator = true }); addAction(tr::lng_peer_gifts_filter_saved(tr::now), [=] { diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 568d449f4..3de238c6b 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -535,6 +535,7 @@ inputPrivacyKeyVoiceMessages#aee69d68 = InputPrivacyKey; inputPrivacyKeyAbout#3823cc40 = InputPrivacyKey; inputPrivacyKeyBirthday#d65a11cc = InputPrivacyKey; inputPrivacyKeyStarGiftsAutoSave#e1732341 = InputPrivacyKey; +inputPrivacyKeyNoPaidMessages#bdc597b4 = InputPrivacyKey; privacyKeyStatusTimestamp#bc2eab30 = PrivacyKey; privacyKeyChatInvite#500e6dfa = PrivacyKey; diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp index 09ae65ad8..c605ed28d 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp @@ -356,10 +356,16 @@ void AddMessagesPrivacyButton( not_null container) { const auto session = &controller->session(); const auto privacy = &session->api().globalPrivacy(); - auto label = rpl::conditional( + auto label = rpl::combine( privacy->newRequirePremium(), - tr::lng_edit_privacy_contacts_and_premium(), - tr::lng_edit_privacy_everyone()); + privacy->newChargeStars() + ) | rpl::map([=](bool requirePremium, int chargeStars) { + return chargeStars + ? tr::lng_edit_privacy_paid() + : requirePremium + ? tr::lng_edit_privacy_contacts_and_premium() + : tr::lng_edit_privacy_everyone(); + }) | rpl::flatten_latest(); const auto &st = st::settingsButtonNoIcon; const auto button = AddButtonWithLabel( container, From 45c7829cd8f33d0c16daacfaf8da987b68936bb7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Feb 2025 14:12:19 +0400 Subject: [PATCH 040/103] Track stars-per-message for users and channels. --- .../SourceFiles/boxes/edit_privacy_box.cpp | 137 +++++++++++------- Telegram/SourceFiles/boxes/edit_privacy_box.h | 5 + .../boxes/peers/edit_peer_info_box.cpp | 32 +++- .../boxes/peers/edit_peer_permissions_box.cpp | 62 ++++++-- .../boxes/peers/edit_peer_permissions_box.h | 1 + Telegram/SourceFiles/data/data_changes.h | 65 +++++---- Telegram/SourceFiles/data/data_channel.cpp | 17 +++ Telegram/SourceFiles/data/data_channel.h | 5 + Telegram/SourceFiles/data/data_user.cpp | 13 ++ Telegram/SourceFiles/data/data_user.h | 4 + 10 files changed, 239 insertions(+), 102 deletions(-) diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp index 818ed5c35..f13b5bae0 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp @@ -45,9 +45,9 @@ namespace { constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value; constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value; constexpr auto kGetPercent = 85; -constexpr auto kStarsMin = 10; +constexpr auto kStarsMin = 1; constexpr auto kStarsMax = 9000; -constexpr auto kDefaultChargeStars = 200; +constexpr auto kDefaultChargeStars = 10; using Exceptions = Api::UserPrivacy::Exceptions; @@ -934,7 +934,9 @@ void EditMessagesPrivacyBox( Ui::AddSkip(inner, st::messagePrivacyTopSkip); Ui::AddSubsectionTitle(inner, tr::lng_messages_privacy_subtitle()); const auto group = std::make_shared( - (privacy->newRequirePremiumCurrent() + (!allowed() + ? kOptionAll + : privacy->newRequirePremiumCurrent() ? kOptionPremium : privacy->newChargeStarsCurrent() ? kOptionCharge @@ -984,66 +986,17 @@ void EditMessagesPrivacyBox( const auto chargeInner = chargeWrap->entity(); Ui::AddSkip(chargeInner); - Ui::AddSubsectionTitle(chargeInner, tr::lng_messages_privacy_price()); struct State { rpl::variable stars; }; const auto state = std::make_shared(); const auto savedValue = privacy->newChargeStarsCurrent(); - const auto chargeStars = savedValue ? savedValue : kDefaultChargeStars; - state->stars = chargeStars; - auto values = std::vector(); - if (chargeStars < kStarsMin) { - values.push_back(chargeStars); - } - for (auto i = kStarsMin; i < 100; ++i) { - values.push_back(i); - } - for (auto i = 100; i < 1000; i += 10) { - if (i < chargeStars + 10 && chargeStars < i) { - values.push_back(chargeStars); - } - values.push_back(i); - } - for (auto i = 1000; i < kStarsMax + 1; i += 100) { - if (i < chargeStars + 100 && chargeStars < i) { - values.push_back(chargeStars); - } - values.push_back(i); - } - const auto valuesCount = int(values.size()); - const auto setStars = [=](int value) { - state->stars = value; - }; - chargeInner->add( - MakeChargeStarsSlider( - chargeInner, - &st::settingsScale, - &st::settingsScaleLabel, - valuesCount, - [=](int index) { return values[index]; }, - chargeStars, - setStars, - setStars), - st::boxRowPadding); - Ui::AddSkip(chargeInner); - - auto dollars = state->stars.value() | rpl::map([=](int stars) { - const auto ratio = controller->session().appConfig().get( - u"stars_usd_withdraw_rate_x1000"_q, - 1200); - const auto dollars = int(base::SafeRound(stars * (ratio / 1000.))); - return '~' + Ui::FillAmountAndCurrency(dollars, u"USD"_q); - }); - Ui::AddDividerText( + state->stars = SetupChargeSlider( chargeInner, - tr::lng_messages_privacy_price_about( - lt_percent, - rpl::single(QString::number(kGetPercent) + '%'), - lt_amount, - std::move(dollars))); + controller->session().user(), + savedValue); Ui::AddSkip(chargeInner); Ui::AddSubsectionTitle( @@ -1137,3 +1090,77 @@ void EditMessagesPrivacyBox( }); } } + +rpl::producer SetupChargeSlider( + not_null container, + not_null peer, + int savedValue) { + struct State { + rpl::variable stars; + }; + const auto group = !peer->isUser(); + const auto state = container->lifetime().make_state(); + const auto chargeStars = savedValue ? savedValue : kDefaultChargeStars; + state->stars = chargeStars; + + Ui::AddSubsectionTitle(container, group + ? tr::lng_rights_charge_price() + : tr::lng_messages_privacy_price()); + + auto values = std::vector(); + if (chargeStars < kStarsMin) { + values.push_back(chargeStars); + } + for (auto i = kStarsMin; i < 100; ++i) { + values.push_back(i); + } + for (auto i = 100; i < 1000; i += 10) { + if (i < chargeStars + 10 && chargeStars < i) { + values.push_back(chargeStars); + } + values.push_back(i); + } + for (auto i = 1000; i < kStarsMax + 1; i += 100) { + if (i < chargeStars + 100 && chargeStars < i) { + values.push_back(chargeStars); + } + values.push_back(i); + } + const auto valuesCount = int(values.size()); + const auto setStars = [=](int value) { + state->stars = value; + }; + container->add( + MakeChargeStarsSlider( + container, + &st::settingsScale, + &st::settingsScaleLabel, + valuesCount, + [=](int index) { return values[index]; }, + chargeStars, + setStars, + setStars), + st::boxRowPadding); + + const auto skip = 2 * st::defaultVerticalListSkip; + Ui::AddSkip(container, skip); + + auto dollars = state->stars.value() | rpl::map([=](int stars) { + const auto ratio = peer->session().appConfig().get( + u"stars_usd_withdraw_rate_x1000"_q, + 1200); + const auto dollars = int(base::SafeRound(stars * (ratio / 1000.))); + return '~' + Ui::FillAmountAndCurrency(dollars, u"USD"_q); + }); + Ui::AddDividerText( + container, + (group + ? tr::lng_rights_charge_price_about + : tr::lng_messages_privacy_price_about)( + lt_percent, + rpl::single(QString::number(kGetPercent) + '%'), + lt_amount, + std::move(dollars))); + + return state->stars.value(); +} diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.h b/Telegram/SourceFiles/boxes/edit_privacy_box.h index bd87e90f2..256ebe5b5 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.h +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.h @@ -169,3 +169,8 @@ private: void EditMessagesPrivacyBox( not_null box, not_null controller); + +[[nodiscard]] rpl::producer SetupChargeSlider( + not_null container, + not_null peer, + int savedValue); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 6101f980a..0299e7f9b 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -219,6 +219,33 @@ void SaveSlowmodeSeconds( api->registerModifyRequest(key, requestId); } +void SaveStarsPerMessage( + not_null channel, + int starsPerMessage, + Fn done) { + const auto api = &channel->session().api(); + const auto key = Api::RequestKey("stars_per_message", channel->id); + + const auto requestId = api->request(MTPchannels_UpdatePaidMessagesPrice( + channel->inputChannel, + MTP_long(starsPerMessage) + )).done([=](const MTPUpdates &result) { + api->clearModifyRequest(key); + api->applyUpdates(result); + channel->setStarsPerMessage(starsPerMessage); + done(); + }).fail([=](const MTP::Error &error) { + api->clearModifyRequest(key); + if (error.type() != u"CHAT_NOT_MODIFIED"_q) { + return; + } + channel->setStarsPerMessage(starsPerMessage); + done(); + }).send(); + + api->registerModifyRequest(key, requestId); +} + void SaveBoostsUnrestrict( not_null channel, int boostsUnrestrict, @@ -271,6 +298,7 @@ void ShowEditPermissions( channel, result.boostsUnrestrict, close); + SaveStarsPerMessage(channel, result.starsPerMessage, close); } }; auto done = [=](EditPeerPermissionsBoxResult result) { @@ -282,7 +310,9 @@ void ShowEditPermissions( const auto saveFor = peer->migrateToOrMe(); const auto chat = saveFor->asChat(); if (!chat - || (!result.slowmodeSeconds && !result.boostsUnrestrict)) { + || (!result.slowmodeSeconds + && !result.boostsUnrestrict + && !result.starsPerMessage)) { save(saveFor, result); return; } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index e703994f5..ecb0b8e76 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/profile/info_profile_values.h" #include "boxes/peers/edit_participants_box.h" #include "boxes/peers/edit_peer_info_box.h" +#include "boxes/edit_privacy_box.h" #include "settings/settings_power_saving.h" #include "window/window_session_controller.h" #include "window/window_controller.h" @@ -940,9 +941,7 @@ rpl::producer AddBoostsUnrestrictSlider( const auto boostsUnrestrict = lifetime.make_state>( channel ? channel->boostsUnrestrict() : 0); - container->add( - object_ptr(container), - { 0, st::infoProfileSkip, 0, st::infoProfileSkip }); + Ui::AddSkip(container); auto enabled = boostsUnrestrict->value( ) | rpl::map(_1 > 0); @@ -1006,19 +1005,20 @@ rpl::producer AddBoostsUnrestrictWrapped( object_ptr>( container, object_ptr(container))); - wrap->toggleOn(rpl::duplicate(shown), anim::type::normal); + wrap->toggleOn(std::move(shown), anim::type::normal); wrap->finishAnimating(); - auto result = AddBoostsUnrestrictSlider(wrap->entity(), peer); - const auto divider = container->add( + const auto inner = wrap->entity(); + + auto result = AddBoostsUnrestrictSlider(inner, peer); + + const auto skip = st::defaultVerticalListSkip; + const auto divider = inner->add( object_ptr>( - container, - object_ptr(container), - QMargins{ 0, st::infoProfileSkip, 0, st::infoProfileSkip })); - divider->toggleOn(rpl::combine( - std::move(shown), - rpl::duplicate(result), - !rpl::mappers::_1 || !rpl::mappers::_2)); + inner, + object_ptr(inner), + QMargins{ 0, skip, 0, skip })); + divider->toggleOn(rpl::duplicate(result) | rpl::map(!rpl::mappers::_1)); divider->finishAnimating(); return result; @@ -1157,7 +1157,38 @@ void ShowEditPeerPermissionsBox( rpl::variable slowmodeSeconds; rpl::variable boostsUnrestrict; rpl::variable hasSendRestrictions; + rpl::variable starsPerMessage; }; + const auto state = inner->lifetime().make_state(); + + Ui::AddSkip(inner); + Ui::AddDivider(inner); + Ui::AddSkip(inner); + const auto starsPerMessage = peer->isChannel() + ? peer->asChannel()->starsPerMessage() + : 0; + const auto charging = inner->add(object_ptr( + inner, + tr::lng_rights_charge_stars(), + st::settingsButtonNoIcon)); + charging->toggleOn(rpl::single(starsPerMessage > 0)); + Ui::AddSkip(inner); + Ui::AddDividerText(inner, tr::lng_rights_charge_stars_about()); + + const auto chargeWrap = inner->add( + object_ptr>( + inner, + object_ptr(inner))); + chargeWrap->toggleOn(charging->toggledValue()); + chargeWrap->finishAnimating(); + const auto chargeInner = chargeWrap->entity(); + + Ui::AddSkip(chargeInner); + state->starsPerMessage = SetupChargeSlider( + chargeInner, + peer, + starsPerMessage); + static constexpr auto kSendRestrictions = Flag::EmbedLinks | Flag::SendGames | Flag::SendGifs @@ -1171,7 +1202,6 @@ void ShowEditPeerPermissionsBox( | Flag::SendVoiceMessages | Flag::SendFiles | Flag::SendOther; - const auto state = inner->lifetime().make_state(); state->hasSendRestrictions = ((restrictions & kSendRestrictions) != 0) || (peer->isChannel() && peer->asChannel()->slowmodeSeconds() > 0); state->boostsUnrestrict = AddBoostsUnrestrictWrapped( @@ -1212,10 +1242,14 @@ void ShowEditPeerPermissionsBox( const auto boostsUnrestrict = hasRestrictions ? state->boostsUnrestrict.current() : 0; + const auto starsPerMessage = charging->toggled() + ? state->starsPerMessage.current() + : 0; done({ restrictions, slowmodeSeconds, boostsUnrestrict, + starsPerMessage, }); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h index ce45f4e68..6c4863b7b 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h @@ -39,6 +39,7 @@ struct EditPeerPermissionsBoxResult final { ChatRestrictions rights; int slowmodeSeconds = 0; int boostsUnrestrict = 0; + int starsPerMessage = 0; }; void ShowEditPeerPermissionsBox( diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index cff431720..9e19fc617 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -75,46 +75,47 @@ struct PeerUpdate { BackgroundEmoji = (1ULL << 15), StoriesState = (1ULL << 16), VerifyInfo = (1ULL << 17), + StarsPerMessage = (1ULL << 18), // For users - CanShareContact = (1ULL << 18), - IsContact = (1ULL << 19), - PhoneNumber = (1ULL << 20), - OnlineStatus = (1ULL << 21), - BotCommands = (1ULL << 22), - BotCanBeInvited = (1ULL << 23), - BotStartToken = (1ULL << 24), - CommonChats = (1ULL << 25), - PeerGifts = (1ULL << 26), - HasCalls = (1ULL << 27), - SupportInfo = (1ULL << 28), - IsBot = (1ULL << 29), - EmojiStatus = (1ULL << 30), - BusinessDetails = (1ULL << 31), - Birthday = (1ULL << 32), - PersonalChannel = (1ULL << 33), - StarRefProgram = (1ULL << 34), + CanShareContact = (1ULL << 19), + IsContact = (1ULL << 20), + PhoneNumber = (1ULL << 21), + OnlineStatus = (1ULL << 22), + BotCommands = (1ULL << 23), + BotCanBeInvited = (1ULL << 24), + BotStartToken = (1ULL << 25), + CommonChats = (1ULL << 26), + PeerGifts = (1ULL << 27), + HasCalls = (1ULL << 28), + SupportInfo = (1ULL << 29), + IsBot = (1ULL << 30), + EmojiStatus = (1ULL << 31), + BusinessDetails = (1ULL << 32), + Birthday = (1ULL << 33), + PersonalChannel = (1ULL << 34), + StarRefProgram = (1ULL << 35), // For chats and channels - InviteLinks = (1ULL << 35), - Members = (1ULL << 36), - Admins = (1ULL << 37), - BannedUsers = (1ULL << 38), - Rights = (1ULL << 39), - PendingRequests = (1ULL << 40), - Reactions = (1ULL << 41), + InviteLinks = (1ULL << 36), + Members = (1ULL << 37), + Admins = (1ULL << 38), + BannedUsers = (1ULL << 39), + Rights = (1ULL << 40), + PendingRequests = (1ULL << 41), + Reactions = (1ULL << 42), // For channels - ChannelAmIn = (1ULL << 42), - StickersSet = (1ULL << 43), - EmojiSet = (1ULL << 44), - ChannelLinkedChat = (1ULL << 45), - ChannelLocation = (1ULL << 46), - Slowmode = (1ULL << 47), - GroupCall = (1ULL << 48), + ChannelAmIn = (1ULL << 43), + StickersSet = (1ULL << 44), + EmojiSet = (1ULL << 45), + ChannelLinkedChat = (1ULL << 46), + ChannelLocation = (1ULL << 47), + Slowmode = (1ULL << 48), + GroupCall = (1ULL << 49), // For iteration - LastUsedBit = (1ULL << 48), + LastUsedBit = (1ULL << 49), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 79b9fe4e6..3d10c3579 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -859,6 +859,21 @@ void ChannelData::growSlowmodeLastMessage(TimeId when) { session().changes().peerUpdated(this, UpdateFlag::Slowmode); } +int ChannelData::starsPerMessage() const { + if (const auto info = mgInfo.get()) { + return info->starsPerMessage; + } + return 0; +} + +void ChannelData::setStarsPerMessage(int stars) { + if (!mgInfo || starsPerMessage() == stars) { + return; + } + mgInfo->starsPerMessage = stars; + session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); +} + int ChannelData::peerGiftsCount() const { return _peerGiftsCount; } @@ -1209,6 +1224,8 @@ void ApplyChannelUpdate( } else { channel->setLinkedChat(nullptr); } + channel->setStarsPerMessage( + update.vsend_paid_messages_stars().value_or_empty()); if (const auto history = channel->owner().historyLoaded(channel)) { if (const auto available = update.vavailable_min_id()) { history->clearUpTill(available->v); diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 620cca9d1..c49ada9ac 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -145,6 +145,8 @@ public: int slowmodeSeconds = 0; TimeId slowmodeLastMessage = 0; + int starsPerMessage = 0; + private: ChatData *_migratedFrom = nullptr; ChannelLocation _location; @@ -456,6 +458,9 @@ public: [[nodiscard]] TimeId slowmodeLastMessage() const; void growSlowmodeLastMessage(TimeId when); + [[nodiscard]] int starsPerMessage() const; + void setStarsPerMessage(int stars); + [[nodiscard]] int peerGiftsCount() const; void setPeerGiftsCount(int count); diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 0c36d9945..ac761e7ae 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -532,6 +532,17 @@ bool UserData::readDatesPrivate() const { return (flags() & UserDataFlag::ReadDatesPrivate); } +int UserData::starsPerMessage() const { + return _starsPerMessage; +} + +void UserData::setStarsPerMessage(int stars) { + if (_starsPerMessage != stars) { + _starsPerMessage = stars; + session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); + } +} + bool UserData::canAddContact() const { return canShareThisContact() && !isContact(); } @@ -722,6 +733,8 @@ void ApplyUserUpdate(not_null user, const MTPDuserFull &update) { user->setTranslationDisabled(update.is_translations_disabled()); user->setPrivateForwardName( update.vprivate_forward_name().value_or_empty()); + user->setStarsPerMessage( + update.vsend_paid_messages_stars().value_or_empty()); if (const auto info = user->botInfo.get()) { const auto group = update.vbot_group_admin_rights() diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index c7a200e33..637c6a170 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -179,6 +179,9 @@ public: [[nodiscard]] bool canSendIgnoreRequirePremium() const; [[nodiscard]] bool readDatesPrivate() const; + [[nodiscard]] int starsPerMessage() const; + void setStarsPerMessage(int stars); + [[nodiscard]] bool canShareThisContact() const; [[nodiscard]] bool canAddContact() const; @@ -268,6 +271,7 @@ private: Data::Birthday _birthday; int _commonChatsCount = 0; int _peerGiftsCount = 0; + int _starsPerMessage = 0; ContactStatus _contactStatus = ContactStatus::Unknown; CallsStatus _callsStatus = CallsStatus::Unknown; From bbc14ba74fc6e6c8d09e916515993de2a6ff3beb Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Feb 2025 19:03:11 +0400 Subject: [PATCH 041/103] Allow sending paid messages. --- Telegram/Resources/langs/lang.strings | 7 +- Telegram/SourceFiles/api/api_common.h | 1 - Telegram/SourceFiles/api/api_polls.cpp | 5 +- Telegram/SourceFiles/api/api_sending.cpp | 15 +- Telegram/SourceFiles/apiwrap.cpp | 26 +-- Telegram/SourceFiles/boxes/share_box.cpp | 7 +- .../chat_helpers/chat_helpers.style | 4 + .../SourceFiles/core/click_handler_types.cpp | 4 +- Telegram/SourceFiles/data/data_channel.cpp | 52 +++++- Telegram/SourceFiles/data/data_channel.h | 14 +- Telegram/SourceFiles/data/data_peer.cpp | 43 +++++ Telegram/SourceFiles/data/data_peer.h | 6 + Telegram/SourceFiles/data/data_user.cpp | 37 ++++ Telegram/SourceFiles/data/data_user.h | 8 +- Telegram/SourceFiles/history/history_item.cpp | 18 +- .../SourceFiles/history/history_widget.cpp | 155 ++++++++++++++-- Telegram/SourceFiles/history/history_widget.h | 5 + .../view/history_view_replies_section.h | 1 + .../inline_bots/bot_attach_web_view.cpp | 8 +- .../media/stories/media_stories_share.cpp | 5 +- .../SourceFiles/payments/payments_form.cpp | 4 +- .../settings/settings_credits_graphics.cpp | 7 + .../settings/settings_credits_graphics.h | 6 +- .../SourceFiles/storage/storage_account.cpp | 172 ++++++++++-------- .../SourceFiles/storage/storage_account.h | 30 +-- 25 files changed, 497 insertions(+), 143 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index be5e8e8b6..95da224c6 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2789,6 +2789,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_small_balance_reaction" = "Buy **Stars** and send them to {channel} to support their posts."; "lng_credits_small_balance_subscribe" = "Buy **Stars** and subscribe to **{channel}** and other channels."; "lng_credits_small_balance_star_gift" = "Buy **Stars** to send gifts to {user} and other contacts."; +"lng_credits_small_balance_for_message" = "Buy **Stars** to send messages to {user}."; "lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram."; "lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars."; "lng_credits_enough" = "You have enough stars at the moment. {link}"; @@ -4821,8 +4822,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_slowmode_seconds#one" = "{count} second"; "lng_slowmode_seconds#other" = "{count} seconds"; -"lng_payment_for_message#one" = "Pay {star}{count} for 1 Message"; -"lng_payment_for_message#other" = "Pay {star}{count} for 1 Message"; +"lng_payment_for_message" = "Pay {cost} for 1 Message"; "lng_payment_confirm_title" = "Confirm payment"; "lng_payment_confirm_text#one" = "{name} charges **{count}** Star per message."; "lng_payment_confirm_text#other" = "{name} charges **{count}** Stars per message."; @@ -4830,8 +4830,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_payment_confirm_sure#other" = "Would you like to pay **{count}** Stars to send one message?"; "lng_payment_confirm_dont_ask" = "Don't ask me again"; "lng_payment_confirm_button" = "Pay for 1 Message"; -"lng_payment_bar_text#one" = "{name} must pay {star}{count} for each message to you."; -"lng_payment_bar_text#other" = "{name} must pay {star}{count} for each message to you."; +"lng_payment_bar_text" = "{name} must pay {cost} for each message to you."; "lng_payment_bar_button" = "Remove Fee"; "lng_payment_refund_title" = "Remove Fee"; "lng_payment_refund_text" = "Are you sure you want to allow {name} to message you for free?"; diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index 5a8347e86..77c30d095 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -21,7 +21,6 @@ inline constexpr auto kScheduledUntilOnlineTimestamp = TimeId(0x7FFFFFFE); struct SendOptions { uint64 price = 0; - int64 paidByStars = 0; PeerData *sendAs = nullptr; TimeId scheduled = 0; BusinessShortcutId shortcutId = 0; diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp index e1a63c853..aa03ee94d 100644 --- a/Telegram/SourceFiles/api/api_polls.cpp +++ b/Telegram/SourceFiles/api/api_polls.cpp @@ -59,6 +59,7 @@ void Polls::create( history->startSavingCloudDraft(topicRootId); } const auto silentPost = ShouldSendSilent(peer, action.options); + const auto starsPaid = peer->commitStarsForMessage(); if (silentPost) { sendFlags |= MTPmessages_SendMedia::Flag::f_silent; } @@ -71,7 +72,7 @@ void Polls::create( if (action.options.effectId) { sendFlags |= MTPmessages_SendMedia::Flag::f_effect; } - if (action.options.paidByStars) { + if (starsPaid) { sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; } const auto sendAs = action.options.sendAs; @@ -97,7 +98,7 @@ void Polls::create( (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, action.options.shortcutId), MTP_long(action.options.effectId), - MTP_long(action.options.paidByStars) + MTP_long(starsPaid) ), [=](const MTPUpdates &result, const MTP::Response &response) { if (clearCloudDraft) { history->finishSavingCloudDraft( diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 124ba63f9..319032aed 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -95,6 +95,7 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { const auto messagePostAuthor = peer->isBroadcast() ? session->user()->name() : QString(); + const auto starsPaid = peer->commitStarsForMessage(); if (action.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; @@ -111,7 +112,7 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { flags |= MessageFlag::InvertMedia; sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } - if (action.options.paidByStars) { + if (starsPaid) { sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; } @@ -133,7 +134,7 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(session, action.options.shortcutId), MTP_long(action.options.effectId), - MTP_long(action.options.paidByStars) + MTP_long(starsPaid) ), [=](const MTPUpdates &result, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) { api->sendMessageFail(error, peer, randomId); @@ -194,6 +195,7 @@ void SendExistingMedia( sendFlags |= MTPmessages_SendMedia::Flag::f_entities; } const auto captionText = caption.text; + const auto starsPaid = peer->commitStarsForMessage(); if (action.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; @@ -210,7 +212,7 @@ void SendExistingMedia( flags |= MessageFlag::InvertMedia; sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } - if (action.options.paidByStars) { + if (starsPaid) { sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; } @@ -248,7 +250,7 @@ void SendExistingMedia( (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(session, action.options.shortcutId), MTP_long(action.options.effectId), - MTP_long(action.options.paidByStars) + MTP_long(starsPaid) ), [=](const MTPUpdates &result, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) { if (error.code() == 400 @@ -388,7 +390,8 @@ bool SendDice(MessageToSend &message) { flags |= MessageFlag::InvertMedia; sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } - if (action.options.paidByStars) { + const auto starsPaid = peer->commitStarsForMessage(); + if (starsPaid) { sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; } @@ -423,7 +426,7 @@ bool SendDice(MessageToSend &message) { (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(session, action.options.shortcutId), MTP_long(action.options.effectId), - MTP_long(action.options.paidByStars) + MTP_long(starsPaid) ), [=](const MTPUpdates &result, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) { api->sendMessageFail(error, peer, randomId, newId); diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index a56722212..4362f704b 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3883,7 +3883,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) { sendFlags |= MTPmessages_SendMessage::Flag::f_effect; mediaFlags |= MTPmessages_SendMedia::Flag::f_effect; } - if (action.options.paidByStars) { + const auto starsPaid = peer->commitStarsForMessage(); + if (starsPaid) { sendFlags |= MTPmessages_SendMessage::Flag::f_allow_paid_stars; mediaFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; } @@ -3943,7 +3944,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { (sendAs ? sendAs->input : MTP_inputPeerEmpty()), mtpShortcut, MTP_long(action.options.effectId), - MTP_long(action.options.paidByStars) + MTP_long(starsPaid) ), done, fail); } else { histories.sendPreparedMessage( @@ -3962,7 +3963,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { (sendAs ? sendAs->input : MTP_inputPeerEmpty()), mtpShortcut, MTP_long(action.options.effectId), - MTP_long(action.options.paidByStars) + MTP_long(starsPaid) ), done, fail); } isFirst = false; @@ -4059,7 +4060,8 @@ void ApiWrap::sendInlineResult( if (action.options.hideViaBot) { sendFlags |= SendFlag::f_hide_via; } - if (action.options.paidByStars) { + const auto starsPaid = peer->commitStarsForMessage(); + if (starsPaid) { sendFlags |= SendFlag::f_allow_paid_stars; } @@ -4100,7 +4102,7 @@ void ApiWrap::sendInlineResult( MTP_int(action.options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, action.options.shortcutId), - MTP_long(action.options.paidByStars) + MTP_long(starsPaid) ), [=](const MTPUpdates &result, const MTP::Response &response) { history->finishSavingCloudDraft( topicRootId, @@ -4232,6 +4234,7 @@ void ApiWrap::sendMediaWithRandomId( Fn done) { const auto history = item->history(); const auto replyTo = item->replyTo(); + const auto peer = history->peer; auto caption = item->originalText(); TextUtilities::Trim(caption); @@ -4241,6 +4244,7 @@ void ApiWrap::sendMediaWithRandomId( Api::ConvertOption::SkipLocal); const auto updateRecentStickers = Api::HasAttachedStickers(media); + const auto starsPaid = peer->commitStarsForMessage(); using Flag = MTPmessages_SendMedia::Flag; const auto flags = Flag(0) @@ -4254,10 +4258,9 @@ void ApiWrap::sendMediaWithRandomId( | (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)) | (options.effectId ? Flag::f_effect : Flag(0)) | (options.invertCaption ? Flag::f_invert_media : Flag(0)) - | (options.paidByStars ? Flag::f_allow_paid_stars : Flag(0)); + | (starsPaid ? Flag::f_allow_paid_stars : Flag(0)); auto &histories = history->owner().histories(); - const auto peer = history->peer; const auto itemId = item->fullId(); histories.sendPreparedMessage( history, @@ -4282,7 +4285,7 @@ void ApiWrap::sendMediaWithRandomId( (options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, options.shortcutId), MTP_long(options.effectId), - MTP_long(options.paidByStars) + MTP_long(starsPaid) ), [=](const MTPUpdates &result, const MTP::Response &response) { if (done) done(true); if (updateRecentStickers) { @@ -4311,6 +4314,7 @@ void ApiWrap::sendMultiPaidMedia( const auto history = item->history(); const auto replyTo = item->replyTo(); + const auto peer = history->peer; auto caption = item->originalText(); TextUtilities::Trim(caption); @@ -4318,6 +4322,7 @@ void ApiWrap::sendMultiPaidMedia( _session, caption.entities, Api::ConvertOption::SkipLocal); + const auto starsPaid = peer->commitStarsForMessage(); using Flag = MTPmessages_SendMedia::Flag; const auto flags = Flag(0) @@ -4331,10 +4336,9 @@ void ApiWrap::sendMultiPaidMedia( | (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)) | (options.effectId ? Flag::f_effect : Flag(0)) | (options.invertCaption ? Flag::f_invert_media : Flag(0)) - | (options.paidByStars ? Flag::f_allow_paid_stars : Flag(0)); + | (starsPaid ? Flag::f_allow_paid_stars : Flag(0)); auto &histories = history->owner().histories(); - const auto peer = history->peer; const auto itemId = item->fullId(); album->sent = true; histories.sendPreparedMessage( @@ -4358,7 +4362,7 @@ void ApiWrap::sendMultiPaidMedia( (options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, options.shortcutId), MTP_long(options.effectId), - MTP_long(options.paidByStars) + MTP_long(starsPaid) ), [=](const MTPUpdates &result, const MTP::Response &response) { if (const auto album = _sendingAlbums.take(groupId)) { const auto copy = (*album)->items; diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index fb6822f5d..c88a39f6b 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -1571,6 +1571,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( : topicRootId; const auto peer = thread->peer(); const auto threadHistory = thread->owningHistory(); + const auto starsPaid = peer->commitStarsForMessage(); histories.sendRequest(threadHistory, requestType, [=]( Fn finish) { const auto session = &threadHistory->session(); @@ -1583,9 +1584,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( | (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)) - | (options.paidByStars - ? Flag::f_allow_paid_stars - : Flag()); + | (starsPaid ? Flag::f_allow_paid_stars : Flag()); threadHistory->sendRequestId = api.request( MTPmessages_ForwardMessages( MTP_flags(sendFlags), @@ -1598,7 +1597,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( MTP_inputPeerEmpty(), // send_as Data::ShortcutIdToMTP(session, options.shortcutId), MTP_int(videoTimestamp.value_or(0)), - MTP_long(options.paidByStars) + MTP_long(starsPaid) )).done([=](const MTPUpdates &updates, mtpRequestId reqId) { threadHistory->session().api().applyUpdates(updates); state->requests.remove(reqId); diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 9d13bb099..a380b6d5f 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -855,6 +855,10 @@ historyComposeButton: FlatButton { color: historyComposeButtonBgRipple; } } +historyComposeButtonText: FlatLabel(defaultFlatLabel) { + style: semiboldTextStyle; + textFg: windowActiveTextFg; +} historyGiftToChannel: IconButton(defaultIconButton) { width: 46px; height: 46px; diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index ded39a2f8..9e290988f 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -205,13 +205,13 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const { }); }; if (_bot->isVerified() - || _bot->session().local().isBotTrustedOpenGame(_bot->id)) { + || _bot->session().local().isPeerTrustedOpenGame(_bot->id)) { openGame(); } else { if (const auto controller = my.sessionWindow.get()) { const auto callback = [=, bot = _bot](Fn close) { close(); - bot->session().local().markBotTrustedOpenGame(bot->id); + bot->session().local().markPeerTrustedOpenGame(bot->id); openGame(); }; controller->show(Ui::MakeConfirmBox({ diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 3d10c3579..909823794 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "api/api_global_privacy.h" +#include "data/components/credits.h" #include "data/data_changes.h" #include "data/data_channel_admins.h" #include "data/data_user.h" @@ -30,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_chat_invite.h" #include "api/api_invite_links.h" #include "apiwrap.h" +#include "storage/storage_account.h" #include "ui/unread_badge.h" #include "window/notifications_manager.h" @@ -861,7 +863,7 @@ void ChannelData::growSlowmodeLastMessage(TimeId when) { int ChannelData::starsPerMessage() const { if (const auto info = mgInfo.get()) { - return info->starsPerMessage; + return info->_starsPerMessage; } return 0; } @@ -870,8 +872,54 @@ void ChannelData::setStarsPerMessage(int stars) { if (!mgInfo || starsPerMessage() == stars) { return; } - mgInfo->starsPerMessage = stars; + const auto removed = mgInfo->_starsPerMessage && !stars; + mgInfo->_starsPerMessage = stars; session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); + if (removed) { + session().local().clearPeerTrusted(id); + } +} + +int ChannelData::starsForMessageLocked() const { + if (const auto info = mgInfo.get()) { + return info->_starsForMessageLocked; + } + return 0; +} + +void ChannelData::lockStarsForMessage() { + const auto info = mgInfo.get(); + if (!info || info->_starsForMessageLocked == info->_starsPerMessage) { + return; + } + cancelStarsForMessage(); + if (info->_starsPerMessage) { + info->_starsForMessageLocked = info->_starsPerMessage; + session().credits().lock(StarsAmount(info->_starsPerMessage)); + session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); + } +} + +int ChannelData::commitStarsForMessage() { + const auto info = mgInfo.get(); + if (!info) { + return 0; + } else if (const auto stars = base::take(info->_starsForMessageLocked)) { + session().credits().withdrawLocked(StarsAmount(stars)); + session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); + return stars; + } + return 0; +} + +void ChannelData::cancelStarsForMessage() { + const auto info = mgInfo.get(); + if (!info) { + return; + } else if (const auto stars = base::take(info->_starsForMessageLocked)) { + session().credits().unlock(StarsAmount(stars)); + session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); + } } int ChannelData::peerGiftsCount() const { diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index c49ada9ac..9484f8974 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer_bot_commands.h" #include "data/data_user_names.h" +class ChannelData; + struct ChannelLocation { QString address; Data::LocationPoint point; @@ -145,13 +147,15 @@ public: int slowmodeSeconds = 0; TimeId slowmodeLastMessage = 0; - int starsPerMessage = 0; - private: ChatData *_migratedFrom = nullptr; ChannelLocation _location; Data::ChatBotCommands _botCommands; std::unique_ptr _forum; + int _starsPerMessage = 0; + int _starsForMessageLocked = 0; + + friend class ChannelData; }; @@ -458,8 +462,12 @@ public: [[nodiscard]] TimeId slowmodeLastMessage() const; void growSlowmodeLastMessage(TimeId when); - [[nodiscard]] int starsPerMessage() const; void setStarsPerMessage(int stars); + [[nodiscard]] int starsPerMessage() const; + [[nodiscard]] int starsForMessageLocked() const; + void lockStarsForMessage(); + [[nodiscard]] int commitStarsForMessage(); + void cancelStarsForMessage(); [[nodiscard]] int peerGiftsCount() const; void setPeerGiftsCount(int count); diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index b26234ee6..05e07ef26 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -1450,6 +1450,49 @@ bool PeerData::canManageGroupCall() const { return false; } +int PeerData::starsPerMessage() const { + if (const auto user = asUser()) { + return user->starsPerMessage(); + } else if (const auto channel = asChannel()) { + return channel->starsPerMessage(); + } + return 0; +} + +int PeerData::starsForMessageLocked() const { + if (const auto user = asUser()) { + return user->starsForMessageLocked(); + } else if (const auto channel = asChannel()) { + return channel->starsForMessageLocked(); + } + return 0; +} + +void PeerData::lockStarsForMessage() { + if (const auto user = asUser()) { + user->lockStarsForMessage(); + } else if (const auto channel = asChannel()) { + channel->lockStarsForMessage(); + } +} + +int PeerData::commitStarsForMessage() { + if (const auto user = asUser()) { + return user->commitStarsForMessage(); + } else if (const auto channel = asChannel()) { + return channel->commitStarsForMessage(); + } + return 0; +} + +void PeerData::cancelStarsForMessage() { + if (const auto user = asUser()) { + user->cancelStarsForMessage(); + } else if (const auto channel = asChannel()) { + channel->cancelStarsForMessage(); + } +} + Data::GroupCall *PeerData::groupCall() const { if (const auto chat = asChat()) { return chat->groupCall(); diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 89d5e7d18..b67f3b5b5 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -268,6 +268,12 @@ public: [[nodiscard]] int slowmodeSecondsLeft() const; [[nodiscard]] bool canManageGroupCall() const; + [[nodiscard]] int starsPerMessage() const; + [[nodiscard]] int starsForMessageLocked() const; + void lockStarsForMessage(); + [[nodiscard]] int commitStarsForMessage(); + void cancelStarsForMessage(); + [[nodiscard]] UserData *asBot(); [[nodiscard]] const UserData *asBot() const; [[nodiscard]] UserData *asUser(); diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index ac761e7ae..af333ed6e 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_sensitive_content.h" #include "api/api_statistics.h" #include "storage/localstorage.h" +#include "storage/storage_account.h" #include "storage/storage_user_photos.h" #include "main/main_session.h" #include "data/business/data_business_common.h" @@ -538,8 +539,44 @@ int UserData::starsPerMessage() const { void UserData::setStarsPerMessage(int stars) { if (_starsPerMessage != stars) { + const auto removed = _starsPerMessage && !stars; _starsPerMessage = stars; session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); + if (removed) { + session().local().clearPeerTrusted(id); + } + } +} + +int UserData::starsForMessageLocked() const { + return _starsForMessageLocked; +} + +void UserData::lockStarsForMessage() { + if (_starsPerMessage == _starsForMessageLocked) { + return; + } + cancelStarsForMessage(); + if (_starsPerMessage) { + _starsForMessageLocked = _starsPerMessage; + session().credits().lock(StarsAmount(_starsPerMessage)); + session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); + } +} + +int UserData::commitStarsForMessage() { + if (const auto stars = base::take(_starsForMessageLocked)) { + session().credits().withdrawLocked(StarsAmount(stars)); + session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); + return stars; + } + return 0; +} + +void UserData::cancelStarsForMessage() { + if (const auto stars = base::take(_starsForMessageLocked)) { + session().credits().unlock(StarsAmount(stars)); + session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); } } diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index 637c6a170..54c647033 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "core/stars_amount.h" +#include "data/components/credits.h" #include "data/data_birthday.h" #include "data/data_peer.h" #include "data/data_chat_participant_status.h" @@ -179,8 +180,12 @@ public: [[nodiscard]] bool canSendIgnoreRequirePremium() const; [[nodiscard]] bool readDatesPrivate() const; - [[nodiscard]] int starsPerMessage() const; void setStarsPerMessage(int stars); + [[nodiscard]] int starsPerMessage() const; + [[nodiscard]] int starsForMessageLocked() const; + void lockStarsForMessage(); + [[nodiscard]] int commitStarsForMessage(); + void cancelStarsForMessage(); [[nodiscard]] bool canShareThisContact() const; [[nodiscard]] bool canAddContact() const; @@ -272,6 +277,7 @@ private: int _commonChatsCount = 0; int _peerGiftsCount = 0; int _starsPerMessage = 0; + int _starsForMessageLocked = 0; ContactStatus _contactStatus = ContactStatus::Unknown; CallsStatus _callsStatus = CallsStatus::Unknown; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index cd2e5b724..406ae555a 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -5577,7 +5577,23 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { auto preparePaidMessage = [&]( const MTPDmessageActionPaidMessage &action) { auto result = PreparedServiceText(); - result.text.text = u"paid for message"_q; AssertIsDebug(); + const auto stars = action.vstars().v; + if (_from->isSelf()) { + result.text = tr::lng_action_paid_message_sent( + tr::now, + lt_count, + stars, + Ui::Text::WithEntities); + } else { + result.links.push_back(_from->createOpenLink()); + result.text = tr::lng_action_paid_message_got( + tr::now, + lt_count, + stars, + lt_name, + Ui::Text::Link(_from->shortName(), 1), + Ui::Text::WithEntities); + } return result; }; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 88060ecf4..bb8da7dfd 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_unread_things.h" #include "ui/boxes/confirm_box.h" #include "boxes/delete_messages_box.h" +#include "boxes/send_credits_box.h" #include "boxes/send_files_box.h" #include "boxes/share_box.h" #include "boxes/edit_caption_box.h" @@ -129,6 +130,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/mtproto_config.h" #include "lang/lang_keys.h" #include "settings/business/settings_quick_replies.h" +#include "settings/settings_credits_graphics.h" #include "storage/localimageloader.h" #include "storage/storage_account.h" #include "storage/file_upload.h" @@ -146,6 +148,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_theme.h" #include "ui/chat/chat_style.h" #include "ui/chat/continuous_scroll.h" +#include "ui/widgets/checkbox.h" #include "ui/widgets/elastic_scroll.h" #include "ui/widgets/popup_menu.h" #include "ui/item_text_options.h" @@ -263,6 +266,7 @@ HistoryWidget::HistoryWidget( tr::lng_channel_mute(tr::now).toUpper(), st::historyComposeButton) , _reportMessages(this, QString(), st::historyComposeButton) +, _payForMessage(this, QString(), st::historyComposeButton) , _attachToggle(this, st::historyAttach) , _tabbedSelectorToggle(this, st::historyAttachEmoji) , _botKeyboardShow(this, st::historyBotKeyboardShow) @@ -380,6 +384,7 @@ HistoryWidget::HistoryWidget( _muteUnmute->addClickHandler([=] { toggleMuteUnmute(); }); setupGiftToChannelButton(); _reportMessages->addClickHandler([=] { reportSelectedMessages(); }); + _payForMessage->addClickHandler([=] { payForMessage(); }); _field->submits( ) | rpl::start_with_next([=](Qt::KeyboardModifiers modifiers) { sendWithModifiers(modifiers); @@ -520,6 +525,7 @@ HistoryWidget::HistoryWidget( _joinChannel->hide(); _muteUnmute->hide(); _reportMessages->hide(); + _payForMessage->hide(); initVoiceRecordBar(); @@ -798,6 +804,7 @@ HistoryWidget::HistoryWidget( | PeerUpdateFlag::MessagesTTL | PeerUpdateFlag::ChatThemeEmoji | PeerUpdateFlag::FullInfo + | PeerUpdateFlag::StarsPerMessage ) | rpl::filter([=](const Data::PeerUpdate &update) { return (update.peer.get() == _peer); }) | rpl::map([](const Data::PeerUpdate &update) { @@ -809,7 +816,7 @@ HistoryWidget::HistoryWidget( const auto was = (_sendAs != nullptr); refreshSendAsToggle(); - if (was != (_sendAs != nullptr)) { + if (was != (_sendAs != nullptr) || _peer->starsPerMessage()) { updateControlsVisibility(); updateControlsGeometry(); orderWidgets(); @@ -832,7 +839,8 @@ HistoryWidget::HistoryWidget( return; } } - if (flags & PeerUpdateFlag::BotStartToken) { + if ((flags & PeerUpdateFlag::BotStartToken) + || (flags & PeerUpdateFlag::StarsPerMessage)) { updateControlsVisibility(); updateControlsGeometry(); } @@ -1957,6 +1965,7 @@ void HistoryWidget::setInnerFocus() { || isRecording() || isJoinChannel() || isBotStart() + || isPayForMessage() || isBlocked() || (!_canSendTexts && !_editMsgId)) { if (_scroll->isHidden()) { @@ -2379,6 +2388,9 @@ void HistoryWidget::showHistory( setHistory(nullptr); _list = nullptr; + if (_peer) { + _peer->cancelStarsForMessage(); + } _peer = nullptr; _topicsRequested.clear(); _canSendMessages = false; @@ -3017,16 +3029,14 @@ bool HistoryWidget::contentOverlapped(const QRect &globalRect) { } bool HistoryWidget::canWriteMessage() const { - if (!_history || !_canSendMessages) { - return false; - } - if (isBlocked() || isJoinChannel() || isMuteUnmute() || isBotStart()) { - return false; - } - if (isSearching()) { - return false; - } - return true; + return _history + && _canSendMessages + && !isBlocked() + && !isJoinChannel() + && !isMuteUnmute() + && !isBotStart() + && !isPayForMessage() + && !isSearching(); } void HistoryWidget::updateControlsVisibility() { @@ -3086,6 +3096,7 @@ void HistoryWidget::updateControlsVisibility() { || isJoinChannel() || isMuteUnmute() || isBotStart() + || isPayForMessage() || isReportMessages()))) { const auto toggle = [&](Ui::FlatButton *shown) { const auto toggleOne = [&](not_null button) { @@ -3097,6 +3108,7 @@ void HistoryWidget::updateControlsVisibility() { } }; toggleOne(_reportMessages); + toggleOne(_payForMessage); toggleOne(_joinChannel); toggleOne(_muteUnmute); toggleOne(_botStart); @@ -3110,6 +3122,8 @@ void HistoryWidget::updateControlsVisibility() { toggle(_reportMessages); } else if (isBlocked()) { toggle(_unblock); + } else if (isPayForMessage()) { + toggle(_payForMessage); } else if (isJoinChannel()) { toggle(_joinChannel); } else if (isMuteUnmute()) { @@ -3172,6 +3186,7 @@ void HistoryWidget::updateControlsVisibility() { _joinChannel->hide(); _muteUnmute->hide(); _reportMessages->hide(); + _payForMessage->hide(); _send->show(); updateSendButtonType(); @@ -3285,6 +3300,7 @@ void HistoryWidget::updateControlsVisibility() { _joinChannel->hide(); _muteUnmute->hide(); _reportMessages->hide(); + _payForMessage->hide(); _attachToggle->hide(); if (_silent) { _silent->hide(); @@ -4525,6 +4541,73 @@ void HistoryWidget::reportSelectedMessages() { } } +void HistoryWidget::payForMessage() { + if (!_peer || !session().credits().loaded()) { + return; + } else if (!_peer->starsPerMessage() || _peer->starsForMessageLocked()) { + updateControlsVisibility(); + } else if (session().local().isPeerTrustedPayForMessage(_peer->id)) { + payForMessageSure(); + } else { + const auto peer = _peer; + const auto count = peer->starsPerMessage(); + controller()->show(Box([=](not_null box) { + const auto trust = std::make_shared>(); + const auto confirmed = [=](Fn close) { + payForMessageSure((*trust)->checked()); + close(); + }; + Ui::ConfirmBox(box, { + .text = tr::lng_payment_confirm_text( + tr::now, + lt_count, + count, + lt_name, + Ui::Text::Bold(peer->shortName()), + Ui::Text::RichLangValue).append(' ').append( + tr::lng_payment_confirm_sure( + tr::now, + lt_count, + count, + Ui::Text::RichLangValue)), + .confirmed = confirmed, + .confirmText = tr::lng_payment_confirm_button(), + .title = tr::lng_payment_confirm_title(), + }); + const auto skip = st::defaultCheckbox.margin.top(); + *trust = box->addRow( + object_ptr( + box, + tr::lng_payment_confirm_dont_ask(tr::now)), + st::boxRowPadding + QMargins(0, skip, 0, skip)); + })); + } +} + +void HistoryWidget::payForMessageSure(bool trust) { + if (trust) { + session().local().markPeerTrustedPayForMessage(_peer->id); + } + const auto required = _peer->starsPerMessage(); + if (!required) { + return; + } + const auto done = [=](Settings::SmallBalanceResult result) { + if (result == Settings::SmallBalanceResult::Success + || result == Settings::SmallBalanceResult::Already) { + _peer->lockStarsForMessage(); + if (canWriteMessage()) { + setInnerFocus(); + } + } + }; + Settings::MaybeRequestBalanceIncrease( + controller()->uiShow(), + required, + Settings::SmallBalanceForMessage{ .recipientId = _peer->id }, + crl::guard(this, done)); +} + History *HistoryWidget::history() const { return _history; } @@ -5071,6 +5154,40 @@ bool HistoryWidget::isSearching() const { return _composeSearch != nullptr; } +bool HistoryWidget::isPayForMessage() const { + const auto stars = _peer ? _peer->starsPerMessage() : 0; + const auto locked = _peer ? _peer->starsForMessageLocked() : 0; + if (!stars || locked) { + return false; + } else if (const auto channel = _peer->asChannel()) { + if (channel->amCreator() || channel->adminRights()) { + return false; + } + } + + const auto creating = !_payForMessageStars.current(); + _payForMessageStars = stars; + if (creating) { + session().credits().load(); + + auto text = _payForMessageStars.value( + ) | rpl::map([session = &session()](int stars) { + return tr::lng_payment_for_message( + tr::now, + lt_cost, + Ui::CreditsEmojiSmall(session).append( + Lang::FormatCountDecimal(stars)), + Ui::Text::WithEntities); + }); + Ui::SetButtonMarkedLabel( + _payForMessage, + std::move(text), + &session(), + st::historyComposeButtonText); + } + return true; +} + bool HistoryWidget::showRecordButton() const { return (_recordAvailability != Webrtc::RecordAvailability::None) && !_voiceRecordBar->isListenState() @@ -5128,6 +5245,7 @@ bool HistoryWidget::updateCmdStartShown() { && _peer->asChannel()->mgInfo->botStatus > 0))) { if (!isBotStart() && !isBlocked() + && !isPayForMessage() && !_keyboard->hasMarkup() && !_keyboard->forceReply() && !_editMsgId) { @@ -5541,7 +5659,7 @@ void HistoryWidget::moveFieldControls() { // (_botMenu.button) (_attachToggle|_replaceMedia) (_sendAs) ---- _inlineResults ------------------------------ _tabbedPanel ------ _fieldBarCancel // (_attachDocument|_attachPhoto) _field (_ttlInfo) (_scheduled) (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) _send -// (_botStart|_unblock|_joinChannel|_muteUnmute|_reportMessages) +// (_botStart|_unblock|_joinChannel|_muteUnmute|_reportMessages|_payForMessage) auto buttonsBottom = bottom - _attachToggle->height(); auto left = st::historySendRight; @@ -5615,6 +5733,7 @@ void HistoryWidget::moveFieldControls() { _joinChannel->setGeometry(fullWidthButtonRect); _muteUnmute->setGeometry(fullWidthButtonRect); _reportMessages->setGeometry(fullWidthButtonRect); + _payForMessage->setGeometry(fullWidthButtonRect); if (_sendRestriction) { _sendRestriction->setGeometry(fullWidthButtonRect); } @@ -6055,18 +6174,25 @@ void HistoryWidget::handleHistoryChange(not_null history) { const auto joinChannel = isJoinChannel(); const auto muteUnmute = isMuteUnmute(); const auto reportMessages = isReportMessages(); + const auto payForMessage = isPayForMessage(); const auto update = false || (_reportMessages->isHidden() == reportMessages) || (!reportMessages && _unblock->isHidden() == unblock) || (!reportMessages && !unblock + && _payForMessage->isHidden() == payForMessage) + || (!reportMessages + && !unblock + && !payForMessage && _botStart->isHidden() == botStart) || (!reportMessages && !unblock + && !payForMessage && !botStart && _joinChannel->isHidden() == joinChannel) || (!reportMessages && !unblock + && !payForMessage && !botStart && !joinChannel && _muteUnmute->isHidden() == muteUnmute); @@ -6430,6 +6556,7 @@ void HistoryWidget::updateHistoryGeometry( } else if (!editingMessage() && (isSearching() || isBlocked() + || isPayForMessage() || isBotStart() || isJoinChannel() || isMuteUnmute() @@ -6739,6 +6866,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { if (!isSearching() && !isBotStart() && !isBlocked() + && !isPayForMessage() && _canSendMessages && (wasVisible || (_replyTo && _replyEditMsg) @@ -8615,6 +8743,7 @@ void HistoryWidget::updateTopBarSelection() { || (_list && _list->wasSelectedText()) || isRecording() || isBotStart() + || isPayForMessage() || isBlocked() || (!_canSendTexts && !_editMsgId)) { _list->setFocus(); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 46624566e..a6f61228e 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -422,6 +422,8 @@ private: [[nodiscard]] int computeMaxFieldHeight() const; void toggleMuteUnmute(); void reportSelectedMessages(); + void payForMessage(); + void payForMessageSure(bool trust = false); void showKeyboardHideButton(); void toggleKeyboard(bool manual = true); void startBotCommand(); @@ -640,6 +642,7 @@ private: [[nodiscard]] bool isJoinChannel() const; [[nodiscard]] bool isMuteUnmute() const; [[nodiscard]] bool isReportMessages() const; + [[nodiscard]] bool isPayForMessage() const; bool updateCmdStartShown(); void updateSendButtonType(); [[nodiscard]] bool showRecordButton() const; @@ -778,6 +781,8 @@ private: QPointer _giftToChannelIn; QPointer _giftToChannelOut; object_ptr _reportMessages; + object_ptr _payForMessage; + mutable rpl::variable _payForMessageStars; struct { object_ptr button = { nullptr }; QString text; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 94f9dfba3..8af0e66dd 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -348,6 +348,7 @@ private: std::unique_ptr _composeControls; std::unique_ptr _composeSearch; std::unique_ptr _joinGroup; + std::unique_ptr _payForMessage; std::unique_ptr _topicReopenBar; std::unique_ptr _emptyPainter; bool _skipScrollEvent = false; diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 6f1004548..6cee40d89 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -1015,12 +1015,12 @@ void WebViewInstance::resolveApp( void WebViewInstance::confirmOpen(Fn done) { if (_bot->isVerified() - || _session->local().isBotTrustedOpenWebView(_bot->id)) { + || _session->local().isPeerTrustedOpenWebView(_bot->id)) { done(); return; } const auto callback = [=](Fn close) { - _session->local().markBotTrustedOpenWebView(_bot->id); + _session->local().markPeerTrustedOpenWebView(_bot->id); close(); done(); }; @@ -1052,14 +1052,14 @@ void WebViewInstance::confirmAppOpen( bool forceConfirmation) { if (!forceConfirmation && (_bot->isVerified() - || _session->local().isBotTrustedOpenWebView(_bot->id))) { + || _session->local().isPeerTrustedOpenWebView(_bot->id))) { done(writeAccess); return; } _parentShow->show(Box([=](not_null box) { const auto allowed = std::make_shared(); const auto callback = [=](Fn close) { - _session->local().markBotTrustedOpenWebView(_bot->id); + _session->local().markPeerTrustedOpenWebView(_bot->id); done((*allowed) && (*allowed)->checked()); close(); }; diff --git a/Telegram/SourceFiles/media/stories/media_stories_share.cpp b/Telegram/SourceFiles/media/stories/media_stories_share.cpp index e9b4cd36a..630cfacaf 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_share.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_share.cpp @@ -131,7 +131,8 @@ namespace Media::Stories { if (options.invertCaption) { sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } - if (options.paidByStars) { + const auto starsPaid = peer->commitStarsForMessage(); + if (starsPaid) { sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; } const auto done = [=] { @@ -159,7 +160,7 @@ namespace Media::Stories { MTP_inputPeerEmpty(), Data::ShortcutIdToMTP(session, options.shortcutId), MTP_long(options.effectId), - MTP_long(options.paidByStars) + MTP_long(starsPaid) ), [=]( const MTPUpdates &result, const MTP::Response &response) { diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 7f4e26a9f..790b7a960 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -904,7 +904,7 @@ void Form::submit() { if (index < list.size() && password.isEmpty()) { _updates.fire(TmpPasswordRequired{}); return; - } else if (!_session->local().isBotTrustedPayment(_details.botId)) { + } else if (!_session->local().isPeerTrustedPayment(_details.botId)) { _updates.fire(BotTrustRequired{ .bot = _session->data().user(_details.botId), .provider = _session->data().user(_details.providerId), @@ -1307,7 +1307,7 @@ void Form::acceptTerms() { } void Form::trustBot() { - _session->local().markBotTrustedPayment(_details.botId); + _session->local().markPeerTrustedPayment(_details.botId); } void Form::processShippingOptions(const QVector &data) { diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 9e3153117..3e54ba9cf 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -2090,6 +2090,8 @@ void SmallBalanceBox( return QString(); }, [&](SmallBalanceStarGift value) { return owner->peer(value.recipientId)->shortName(); + }, [&](SmallBalanceForMessage value) { + return owner->peer(value.recipientId)->shortName(); }); auto needed = show->session().credits().balanceValue( @@ -2128,6 +2130,11 @@ void SmallBalanceBox( lt_user, rpl::single(Ui::Text::Bold(name)), Ui::Text::RichLangValue) + : v::is(source) + ? tr::lng_credits_small_balance_for_message( + lt_user, + rpl::single(Ui::Text::Bold(name)), + Ui::Text::RichLangValue) : name.isEmpty() ? tr::lng_credits_small_balance_fallback( Ui::Text::RichLangValue) diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 599ced1f3..59084cb36 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -200,12 +200,16 @@ struct SmallBalanceDeepLink { struct SmallBalanceStarGift { PeerId recipientId; }; +struct SmallBalanceForMessage { + PeerId recipientId; +}; struct SmallBalanceSource : std::variant< SmallBalanceBot, SmallBalanceReaction, SmallBalanceSubscription, SmallBalanceDeepLink, - SmallBalanceStarGift> { + SmallBalanceStarGift, + SmallBalanceForMessage> { using variant::variant; }; diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index 7233e0f45..1472ebf5d 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -86,7 +86,7 @@ enum { // Local Storage Keys lskSavedGifsOld = 0x0e, // no data lskSavedGifs = 0x0f, // no data lskStickersKeys = 0x10, // no data - lskTrustedBots = 0x11, // no data + lskTrustedPeers = 0x11, // no data lskFavedStickers = 0x12, // no data lskExportSettings = 0x13, // no data lskBackgroundOld = 0x14, // no data @@ -220,7 +220,7 @@ base::flat_set Account::collectGoodNames() const { _legacyBackgroundKeyDay, _recentHashtagsAndBotsKey, _exportSettingsKey, - _trustedBotsKey, + _trustedPeersKey, _installedMasksKey, _recentMasksKey, _archivedMasksKey, @@ -308,7 +308,7 @@ Account::ReadMapResult Account::readMapWith( base::flat_map draftsMap; base::flat_map draftCursorsMap; base::flat_map draftsNotReadMap; - quint64 locationsKey = 0, reportSpamStatusesKey = 0, trustedBotsKey = 0; + quint64 locationsKey = 0, reportSpamStatusesKey = 0, trustedPeersKey = 0; quint64 recentStickersKeyOld = 0; quint64 installedStickersKey = 0, featuredStickersKey = 0, recentStickersKey = 0, favedStickersKey = 0, archivedStickersKey = 0; quint64 installedMasksKey = 0, recentMasksKey = 0, archivedMasksKey = 0; @@ -371,8 +371,8 @@ Account::ReadMapResult Account::readMapWith( map.stream >> reportSpamStatusesKey; ClearKey(reportSpamStatusesKey, _basePath); } break; - case lskTrustedBots: { - map.stream >> trustedBotsKey; + case lskTrustedPeers: { + map.stream >> trustedPeersKey; } break; case lskRecentStickersOld: { map.stream >> recentStickersKeyOld; @@ -459,7 +459,7 @@ Account::ReadMapResult Account::readMapWith( _draftsNotReadMap = draftsNotReadMap; _locationsKey = locationsKey; - _trustedBotsKey = trustedBotsKey; + _trustedPeersKey = trustedPeersKey; _recentStickersKeyOld = recentStickersKeyOld; _installedStickersKey = installedStickersKey; _featuredStickersKey = featuredStickersKey; @@ -573,7 +573,7 @@ void Account::writeMap() { if (!_draftsMap.empty()) mapSize += sizeof(quint32) * 2 + _draftsMap.size() * sizeof(quint64) * 2; if (!_draftCursorsMap.empty()) mapSize += sizeof(quint32) * 2 + _draftCursorsMap.size() * sizeof(quint64) * 2; if (_locationsKey) mapSize += sizeof(quint32) + sizeof(quint64); - if (_trustedBotsKey) mapSize += sizeof(quint32) + sizeof(quint64); + if (_trustedPeersKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_recentStickersKeyOld) mapSize += sizeof(quint32) + sizeof(quint64); if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) { mapSize += sizeof(quint32) + 4 * sizeof(quint64); @@ -619,8 +619,8 @@ void Account::writeMap() { if (_locationsKey) { mapData.stream << quint32(lskLocations) << quint64(_locationsKey); } - if (_trustedBotsKey) { - mapData.stream << quint32(lskTrustedBots) << quint64(_trustedBotsKey); + if (_trustedPeersKey) { + mapData.stream << quint32(lskTrustedPeers) << quint64(_trustedPeersKey); } if (_recentStickersKeyOld) { mapData.stream << quint32(lskRecentStickersOld) << quint64(_recentStickersKeyOld); @@ -693,7 +693,7 @@ void Account::reset() { _draftsMap.clear(); _draftCursorsMap.clear(); _draftsNotReadMap.clear(); - _locationsKey = _trustedBotsKey = 0; + _locationsKey = _trustedPeersKey = 0; _recentStickersKeyOld = 0; _installedStickersKey = 0; _featuredStickersKey = 0; @@ -3147,47 +3147,47 @@ void Account::readSelf( } } -void Account::writeTrustedBots() { - if (_trustedBots.empty()) { - if (_trustedBotsKey) { - ClearKey(_trustedBotsKey, _basePath); - _trustedBotsKey = 0; +void Account::writeTrustedPeers() { + if (_trustedPeers.empty()) { + if (_trustedPeersKey) { + ClearKey(_trustedPeersKey, _basePath); + _trustedPeersKey = 0; writeMapDelayed(); } return; } - if (!_trustedBotsKey) { - _trustedBotsKey = GenerateKey(_basePath); + if (!_trustedPeersKey) { + _trustedPeersKey = GenerateKey(_basePath); writeMapQueued(); } - quint32 size = sizeof(qint32) + _trustedBots.size() * sizeof(quint64); + quint32 size = sizeof(qint32) + _trustedPeers.size() * sizeof(quint64); EncryptedDescriptor data(size); - data.stream << qint32(_trustedBots.size()); - for (const auto &[peerId, mask] : _trustedBots) { - // value: 8 bit mask, 56 bit bot peer_id. + data.stream << qint32(_trustedPeers.size()); + for (const auto &[peerId, mask] : _trustedPeers) { + // value: 8 bit mask, 56 bit peer_id. auto value = SerializePeerId(peerId); Assert((value >> 56) == 0); value |= (quint64(mask) << 56); data.stream << value; } - FileWriteDescriptor file(_trustedBotsKey, _basePath); + FileWriteDescriptor file(_trustedPeersKey, _basePath); file.writeEncrypted(data, _localKey); } -void Account::readTrustedBots() { - if (_trustedBotsRead) { +void Account::readTrustedPeers() { + if (_trustedPeersRead) { return; } - _trustedBotsRead = true; - if (!_trustedBotsKey) { + _trustedPeersRead = true; + if (!_trustedPeersKey) { return; } FileReadDescriptor trusted; - if (!ReadEncryptedFile(trusted, _trustedBotsKey, _basePath, _localKey)) { - ClearKey(_trustedBotsKey, _basePath); - _trustedBotsKey = 0; + if (!ReadEncryptedFile(trusted, _trustedPeersKey, _basePath, _localKey)) { + ClearKey(_trustedPeersKey, _basePath); + _trustedPeersKey = 0; writeMapDelayed(); return; } @@ -3197,76 +3197,106 @@ void Account::readTrustedBots() { for (int i = 0; i < size; ++i) { auto value = quint64(); trusted.stream >> value; - const auto mask = base::flags::from_raw( + const auto mask = base::flags::from_raw( uchar(value >> 56)); const auto peerIdSerialized = value & ~(0xFFULL << 56); const auto peerId = DeserializePeerId(peerIdSerialized); - _trustedBots.emplace(peerId, mask); + _trustedPeers.emplace(peerId, mask); } } -void Account::markBotTrustedOpenGame(PeerId botId) { - if (isBotTrustedOpenGame(botId)) { +void Account::markPeerTrustedOpenGame(PeerId peerId) { + if (isPeerTrustedOpenGame(peerId)) { return; } - const auto i = _trustedBots.find(botId); - if (i == end(_trustedBots)) { - _trustedBots.emplace(botId, BotTrustFlag()); + const auto i = _trustedPeers.find(peerId); + if (i == end(_trustedPeers)) { + _trustedPeers.emplace(peerId, PeerTrustFlag()); } else { - i->second &= ~BotTrustFlag::NoOpenGame; + i->second &= ~PeerTrustFlag::NoOpenGame; } - writeTrustedBots(); + writeTrustedPeers(); } -bool Account::isBotTrustedOpenGame(PeerId botId) { - readTrustedBots(); - const auto i = _trustedBots.find(botId); - return (i != end(_trustedBots)) - && ((i->second & BotTrustFlag::NoOpenGame) == 0); +bool Account::isPeerTrustedOpenGame(PeerId peerId) { + readTrustedPeers(); + const auto i = _trustedPeers.find(peerId); + return (i != end(_trustedPeers)) + && ((i->second & PeerTrustFlag::NoOpenGame) == 0); } -void Account::markBotTrustedPayment(PeerId botId) { - if (isBotTrustedPayment(botId)) { +void Account::markPeerTrustedPayment(PeerId peerId) { + if (isPeerTrustedPayment(peerId)) { return; } - const auto i = _trustedBots.find(botId); - if (i == end(_trustedBots)) { - _trustedBots.emplace( - botId, - BotTrustFlag::NoOpenGame | BotTrustFlag::Payment); + const auto i = _trustedPeers.find(peerId); + if (i == end(_trustedPeers)) { + _trustedPeers.emplace( + peerId, + PeerTrustFlag::NoOpenGame | PeerTrustFlag::Payment); } else { - i->second |= BotTrustFlag::Payment; + i->second |= PeerTrustFlag::Payment; } - writeTrustedBots(); + writeTrustedPeers(); } -bool Account::isBotTrustedPayment(PeerId botId) { - readTrustedBots(); - const auto i = _trustedBots.find(botId); - return (i != end(_trustedBots)) - && ((i->second & BotTrustFlag::Payment) != 0); +bool Account::isPeerTrustedPayment(PeerId peerId) { + readTrustedPeers(); + const auto i = _trustedPeers.find(peerId); + return (i != end(_trustedPeers)) + && ((i->second & PeerTrustFlag::Payment) != 0); } -void Account::markBotTrustedOpenWebView(PeerId botId) { - if (isBotTrustedOpenWebView(botId)) { +void Account::markPeerTrustedOpenWebView(PeerId peerId) { + if (isPeerTrustedOpenWebView(peerId)) { return; } - const auto i = _trustedBots.find(botId); - if (i == end(_trustedBots)) { - _trustedBots.emplace( - botId, - BotTrustFlag::NoOpenGame | BotTrustFlag::OpenWebView); + const auto i = _trustedPeers.find(peerId); + if (i == end(_trustedPeers)) { + _trustedPeers.emplace( + peerId, + PeerTrustFlag::NoOpenGame | PeerTrustFlag::OpenWebView); } else { - i->second |= BotTrustFlag::OpenWebView; + i->second |= PeerTrustFlag::OpenWebView; } - writeTrustedBots(); + writeTrustedPeers(); } -bool Account::isBotTrustedOpenWebView(PeerId botId) { - readTrustedBots(); - const auto i = _trustedBots.find(botId); - return (i != end(_trustedBots)) - && ((i->second & BotTrustFlag::OpenWebView) != 0); +bool Account::isPeerTrustedOpenWebView(PeerId peerId) { + readTrustedPeers(); + const auto i = _trustedPeers.find(peerId); + return (i != end(_trustedPeers)) + && ((i->second & PeerTrustFlag::OpenWebView) != 0); +} + +void Account::markPeerTrustedPayForMessage(PeerId peerId) { + if (isPeerTrustedPayForMessage(peerId)) { + return; + } + const auto i = _trustedPeers.find(peerId); + if (i == end(_trustedPeers)) { + _trustedPeers.emplace( + peerId, + PeerTrustFlag::NoOpenGame | PeerTrustFlag::PayForMessage); + } else { + i->second |= PeerTrustFlag::PayForMessage; + } + writeTrustedPeers(); +} + +bool Account::isPeerTrustedPayForMessage(PeerId peerId) { + readTrustedPeers(); + const auto i = _trustedPeers.find(peerId); + return (i != end(_trustedPeers)) + && ((i->second & PeerTrustFlag::PayForMessage) != 0); +} + +void Account::clearPeerTrusted(PeerId peerId) { + const auto i = _trustedPeers.find(peerId); + if (i != end(_trustedPeers)) { + _trustedPeers.erase(i); + writeTrustedPeers(); + } } void Account::enforceModernStorageIdBots() { diff --git a/Telegram/SourceFiles/storage/storage_account.h b/Telegram/SourceFiles/storage/storage_account.h index 497ec55ec..c9e977813 100644 --- a/Telegram/SourceFiles/storage/storage_account.h +++ b/Telegram/SourceFiles/storage/storage_account.h @@ -167,12 +167,15 @@ public: const QByteArray& serialized, int32 streamVersion); - void markBotTrustedOpenGame(PeerId botId); - [[nodiscard]] bool isBotTrustedOpenGame(PeerId botId); - void markBotTrustedPayment(PeerId botId); - [[nodiscard]] bool isBotTrustedPayment(PeerId botId); - void markBotTrustedOpenWebView(PeerId botId); - [[nodiscard]] bool isBotTrustedOpenWebView(PeerId botId); + void markPeerTrustedOpenGame(PeerId peerId); + [[nodiscard]] bool isPeerTrustedOpenGame(PeerId peerId); + void markPeerTrustedPayment(PeerId peerId); + [[nodiscard]] bool isPeerTrustedPayment(PeerId peerId); + void markPeerTrustedOpenWebView(PeerId peerId); + [[nodiscard]] bool isPeerTrustedOpenWebView(PeerId peerId); + void markPeerTrustedPayForMessage(PeerId peerId); + [[nodiscard]] bool isPeerTrustedPayForMessage(PeerId peerId); + void clearPeerTrusted(PeerId peerId); void enforceModernStorageIdBots(); [[nodiscard]] Webview::StorageId resolveStorageIdBots(); @@ -203,12 +206,13 @@ private: IncorrectPasscode, Failed, }; - enum class BotTrustFlag : uchar { + enum class PeerTrustFlag : uchar { NoOpenGame = (1 << 0), Payment = (1 << 1), OpenWebView = (1 << 2), + PayForMessage = (1 << 3), }; - friend inline constexpr bool is_flag_type(BotTrustFlag) { return true; }; + friend inline constexpr bool is_flag_type(PeerTrustFlag) { return true; }; [[nodiscard]] base::flat_set collectGoodNames() const; [[nodiscard]] auto prepareReadSettingsContext() const @@ -261,8 +265,8 @@ private: Data::StickersSetFlags readingFlags = 0); void importOldRecentStickers(); - void readTrustedBots(); - void writeTrustedBots(); + void readTrustedPeers(); + void writeTrustedPeers(); void readMediaLastPlaybackPositions(); void writeMediaLastPlaybackPositions(); @@ -295,7 +299,7 @@ private: Fn()> _downloadsSerialize; FileKey _locationsKey = 0; - FileKey _trustedBotsKey = 0; + FileKey _trustedPeersKey = 0; FileKey _installedStickersKey = 0; FileKey _featuredStickersKey = 0; FileKey _recentStickersKey = 0; @@ -324,8 +328,8 @@ private: qint32 _cacheTotalTimeLimit = 0; qint32 _cacheBigFileTotalTimeLimit = 0; - base::flat_map> _trustedBots; - bool _trustedBotsRead = false; + base::flat_map> _trustedPeers; + bool _trustedPeersRead = false; bool _readingUserSettings = false; bool _recentHashtagsAndBotsWereRead = false; bool _searchSuggestionsRead = false; From 74b71b92b6f676c31506daaff630c25cd1a5f0c2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 17 Feb 2025 11:57:24 +0400 Subject: [PATCH 042/103] Update API scheme on layer 200. --- Telegram/SourceFiles/api/api_updates.cpp | 6 +++-- Telegram/SourceFiles/apiwrap.cpp | 7 +++-- .../data/business/data_shortcut_messages.cpp | 3 ++- .../data/components/scheduled_messages.cpp | 6 +++-- Telegram/SourceFiles/data/data_session.cpp | 3 ++- .../export/data/export_data_types.cpp | 4 --- .../export/data/export_data_types.h | 7 +---- .../export/output/export_output_html.cpp | 5 ---- .../export/output/export_output_json.cpp | 4 --- .../admin_log/history_admin_log_item.cpp | 3 ++- Telegram/SourceFiles/history/history_item.cpp | 26 +------------------ Telegram/SourceFiles/mtproto/scheme/api.tl | 7 +++-- .../settings/settings_privacy_controllers.cpp | 3 ++- 13 files changed, 26 insertions(+), 58 deletions(-) diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 89ece13a5..31a4e73cd 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -1219,7 +1219,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { MTPint(), // quick_reply_shortcut_id MTPlong(), // effect MTPFactCheck(), - MTPint()), // report_delivery_until_date + MTPint(), // report_delivery_until_date + MTPlong()), // paid_message_stars MessageFlags(), NewMessageType::Unread); } break; @@ -1257,7 +1258,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { MTPint(), // quick_reply_shortcut_id MTPlong(), // effect MTPFactCheck(), - MTPint()), // report_delivery_until_date + MTPint(), // report_delivery_until_date + MTPlong()), // paid_message_stars MessageFlags(), NewMessageType::Unread); } break; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 4362f704b..71927f5fc 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -4452,6 +4452,7 @@ void ApiWrap::sendAlbumIfReady(not_null album) { const auto history = sample->history(); const auto replyTo = sample->replyTo(); const auto sendAs = album->options.sendAs; + const auto starsPaid = history->peer->commitStarsForMessage(); using Flag = MTPmessages_SendMultiMedia::Flag; const auto flags = Flag(0) | (replyTo ? Flag::f_reply_to : Flag(0)) @@ -4464,7 +4465,8 @@ void ApiWrap::sendAlbumIfReady(not_null album) { ? Flag::f_quick_reply_shortcut : Flag(0)) | (album->options.effectId ? Flag::f_effect : Flag(0)) - | (album->options.invertCaption ? Flag::f_invert_media : Flag(0)); + | (album->options.invertCaption ? Flag::f_invert_media : Flag(0)) + | (starsPaid ? Flag::f_allow_paid_stars : Flag(0)); auto &histories = history->owner().histories(); const auto peer = history->peer; album->sent = true; @@ -4480,7 +4482,8 @@ void ApiWrap::sendAlbumIfReady(not_null album) { MTP_int(album->options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, album->options.shortcutId), - MTP_long(album->options.effectId) + MTP_long(album->options.effectId), + MTP_long(starsPaid) ), [=](const MTPUpdates &result, const MTP::Response &response) { _sendingAlbums.remove(groupId); }, [=](const MTP::Error &error, const MTP::Response &response) { diff --git a/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp b/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp index c1998a441..ca6f362ed 100644 --- a/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp +++ b/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp @@ -91,7 +91,8 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000); MTP_int(shortcutId), MTP_long(data.veffect().value_or_empty()), (data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck()), - MTP_int(data.vreport_delivery_until_date().value_or_empty())); + MTP_int(data.vreport_delivery_until_date().value_or_empty()), + MTP_long(data.vpaid_message_stars().value_or_empty())); }); } diff --git a/Telegram/SourceFiles/data/components/scheduled_messages.cpp b/Telegram/SourceFiles/data/components/scheduled_messages.cpp index 4d039dc00..cecdf3606 100644 --- a/Telegram/SourceFiles/data/components/scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/components/scheduled_messages.cpp @@ -95,7 +95,8 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000); MTPint(), // quick_reply_shortcut_id MTP_long(data.veffect().value_or_empty()), // effect data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck(), - MTP_int(data.vreport_delivery_until_date().value_or_empty())); + MTP_int(data.vreport_delivery_until_date().value_or_empty()), + MTP_long(data.vpaid_message_stars().value_or_empty())); }); } @@ -269,7 +270,8 @@ void ScheduledMessages::sendNowSimpleMessage( MTPint(), // quick_reply_shortcut_id MTP_long(local->effectId()), // effect MTPFactCheck(), - MTPint()), // report_delivery_until_date + MTPint(), // report_delivery_until_date + MTPlong()), // paid_message_stars localFlags, NewMessageType::Unread); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 3184affec..edfc3ec99 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -4690,7 +4690,8 @@ void Session::insertCheckedServiceNotification( MTPint(), // quick_reply_shortcut_id MTPlong(), // effect MTPFactCheck(), - MTPint()), // report_delivery_until_date + MTPint(), // report_delivery_until_date + MTPlong()), // paid_message_stars localFlags, NewMessageType::Unread); } diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index cb85d0b59..6e1ca060b 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1704,10 +1704,6 @@ ServiceAction ParseServiceAction( .giftId = uint64(gift.vid().v), }; }); - }, [&](const MTPDmessageActionPaidMessage &data) { - result.content = ActionPaidMessage{ - .stars = int64(data.vstars().v), - }; }, [](const MTPDmessageActionEmpty &data) {}); return result; } diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index e1fe647a3..d460ee196 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -662,10 +662,6 @@ struct ActionStarGift { bool limited = false; }; -struct ActionPaidMessage { - int64 stars = 0; -}; - struct ServiceAction { std::variant< v::null_t, @@ -711,8 +707,7 @@ struct ServiceAction { ActionPaymentRefunded, ActionGiftStars, ActionPrizeStars, - ActionStarGift, - ActionPaidMessage> content; + ActionStarGift> content; }; ServiceAction ParseServiceAction( diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index 2f996638b..eda916d0a 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -1367,11 +1367,6 @@ auto HtmlWriter::Wrap::pushMessage( + " sent you a gift of " + QByteArray::number(data.stars) + " Telegram Stars."; - }, [&](const ActionPaidMessage &data) { - return serviceFrom - + " paid for the message " - + QString::number(data.stars).toUtf8() - + " Telegram Stars."; }, [](v::null_t) { return QByteArray(); }); if (!serviceText.isEmpty()) { diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index f53573911..dee8d1e94 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -663,10 +663,6 @@ QByteArray SerializeMessage( push("is_limited", data.limited); push("is_anonymous", data.anonymous); pushBare("gift_text", SerializeText(context, data.text)); - }, [&](const ActionPaidMessage &data) { - pushActor(); - pushAction("send_paid_message"); - push("stars", data.stars); }, [](v::null_t) {}); if (v::is_null(message.action.content)) { diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index 1c6d68050..abf5bfbfb 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -191,7 +191,8 @@ MTPMessage PrepareLogMessage(const MTPMessage &message, TimeId newDate) { MTPint(), // quick_reply_shortcut_id MTP_long(data.veffect().value_or_empty()), MTPFactCheck(), - MTPint()); // report_delivery_until_date + MTPint(), // report_delivery_until_date + MTP_long(data.vpaid_message_stars().value_or_empty())); }); } diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 406ae555a..8bd1c4f72 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -5574,29 +5574,6 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return result; }; - auto preparePaidMessage = [&]( - const MTPDmessageActionPaidMessage &action) { - auto result = PreparedServiceText(); - const auto stars = action.vstars().v; - if (_from->isSelf()) { - result.text = tr::lng_action_paid_message_sent( - tr::now, - lt_count, - stars, - Ui::Text::WithEntities); - } else { - result.links.push_back(_from->createOpenLink()); - result.text = tr::lng_action_paid_message_got( - tr::now, - lt_count, - stars, - lt_name, - Ui::Text::Link(_from->shortName(), 1), - Ui::Text::WithEntities); - } - return result; - }; - setServiceText(action.match( prepareChatAddUserText, prepareChatJoinedByLink, @@ -5645,8 +5622,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { prepareStarGift, prepareStarGiftUnique, PrepareEmptyText, - PrepareErrorText, - preparePaidMessage)); + PrepareErrorText)); // Additional information. applyAction(action); diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 3de238c6b..8f912fbe5 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -116,7 +116,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; -message#96fdbbe9 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true flags2:# offline:flags2.1?true video_processing_pending:flags2.4?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long via_business_bot_id:flags2.0?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int effect:flags2.2?long factcheck:flags2.3?FactCheck report_delivery_until_date:flags2.5?int = Message; +message#eabcdd4d flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true flags2:# offline:flags2.1?true video_processing_pending:flags2.4?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long via_business_bot_id:flags2.0?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int effect:flags2.2?long factcheck:flags2.3?FactCheck report_delivery_until_date:flags2.5?int paid_message_stars:flags2.6?long = Message; messageService#d3d28540 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true reactions_are_possible:flags.9?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction reactions:flags.20?MessageReactions ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; @@ -185,7 +185,6 @@ messageActionGiftStars#45d5b021 flags:# currency:string amount:long stars:long c messageActionPrizeStars#b00c47a2 flags:# unclaimed:flags.0?true stars:long transaction_id:string boost_peer:Peer giveaway_msg_id:int = MessageAction; messageActionStarGift#4717e8a4 flags:# name_hidden:flags.0?true saved:flags.2?true converted:flags.3?true upgraded:flags.5?true refunded:flags.9?true can_upgrade:flags.10?true gift:StarGift message:flags.1?TextWithEntities convert_stars:flags.4?long upgrade_msg_id:flags.5?int upgrade_stars:flags.8?long from_id:flags.11?Peer peer:flags.12?Peer saved_id:flags.12?long = MessageAction; messageActionStarGiftUnique#acdfcb81 flags:# upgrade:flags.0?true transferred:flags.1?true saved:flags.2?true refunded:flags.5?true gift:StarGift can_export_at:flags.3?int transfer_stars:flags.4?long from_id:flags.6?Peer peer:flags.7?Peer saved_id:flags.7?long = MessageAction; -messageActionPaidMessage#5cd2501f stars:long = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -1841,7 +1840,7 @@ starsTransactionPeerAPI#f9677aad = StarsTransactionPeer; starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; -starsTransaction#64dfc926 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true paid_message:flags.19?true id:string stars:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount = StarsTransaction; +starsTransaction#ecd50924 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true id:string stars:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount paid_messages:flags.19?int = StarsTransaction; payments.starsStatus#6c9ce8ed flags:# balance:StarsAmount subscriptions:flags.1?Vector subscriptions_next_offset:flags.2?string subscriptions_missing_balance:flags.4?long history:flags.3?Vector next_offset:flags.0?string chats:Vector users:Vector = payments.StarsStatus; @@ -2218,7 +2217,7 @@ messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool; messages.getUnreadMentions#f107e790 flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; messages.readMentions#36e5bf4d flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory; messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages; -messages.sendMultiMedia#37b74355 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long = Updates; +messages.sendMultiMedia#1bf89d74 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long allow_paid_stars:flags.21?long = Updates; messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile; messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets; messages.getSplitRanges#1cff7e08 = Vector; diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index 950b15b74..a843d7f04 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -203,7 +203,8 @@ AdminLog::OwnedItem GenerateForwardedItem( MTPint(), // quick_reply_shortcut_id MTPlong(), // effect MTPFactCheck(), - MTPint() // report_delivery_until_date + MTPint(), // report_delivery_until_date + MTPlong() // paid_message_stars ).match([&](const MTPDmessage &data) { return history->makeMessage( history->nextNonHistoryEntryId(), From 2e45d9fc6bd8b22dc74e6681e95595873c29a717 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 17 Feb 2025 14:30:47 +0400 Subject: [PATCH 043/103] Allow replacing default shortcuts. --- Telegram/SourceFiles/core/shortcuts.cpp | 49 +++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/core/shortcuts.cpp b/Telegram/SourceFiles/core/shortcuts.cpp index fe7809b84..371ef6a6d 100644 --- a/Telegram/SourceFiles/core/shortcuts.cpp +++ b/Telegram/SourceFiles/core/shortcuts.cpp @@ -168,6 +168,7 @@ private: void set(const QKeySequence &result, Command command, bool replace); void remove(const QString &keys); void remove(const QKeySequence &keys); + void remove(const QKeySequence &keys, Command command); void unregister(base::unique_qptr shortcut); void pruneListened(); @@ -293,7 +294,7 @@ void Manager::change( Command command, std::optional restore) { if (!was.isEmpty()) { - remove(was); + remove(was, command); } if (!now.isEmpty()) { set(now, command, true); @@ -397,6 +398,7 @@ bool Manager::readCustomFile() { const auto entry = (*i).toObject(); const auto keys = entry.constFind(u"keys"_q); const auto command = entry.constFind(u"command"_q); + const auto removed = entry.constFind(u"removed"_q); if (keys == entry.constEnd() || command == entry.constEnd() || !(*keys).isString() @@ -410,7 +412,11 @@ bool Manager::readCustomFile() { const auto name = (*command).toString(); const auto i = CommandByName.find(name); if (i != end(CommandByName)) { - set((*keys).toString(), i->second, true); + if (removed != entry.constEnd() && removed->toBool()) { + remove((*keys).toString(), i->second); + } else { + set((*keys).toString(), i->second, true); + } } else { LOG(("Shortcut Warning: " "could not find shortcut command handler '%1'" @@ -565,12 +571,36 @@ void Manager::writeCustomFile() { } } } - for (const auto &[sequence, command] : _defaults) { - if (!_shortcuts.contains(sequence)) { + const auto has = [&](not_null shortcut, Command command) { + for (auto i = _commandByObject.findFirst(shortcut) + ; i != end(_commandByObject) && i->first == shortcut + ; ++i) { + if (i->second == command) { + return true; + } + } + return false; + }; + for (const auto &[sequence, commands] : _defaults) { + const auto i = _shortcuts.find(sequence); + if (i == end(_shortcuts)) { QJsonObject entry; entry.insert(u"keys"_q, sequence.toString().toLower()); entry.insert(u"command"_q, QJsonValue()); shortcuts.append(entry); + continue; + } + for (const auto command : commands) { + if (!has(i->second.get(), command)) { + const auto j = CommandNames().find(command); + if (j != CommandNames().end()) { + QJsonObject entry; + entry.insert(u"keys"_q, sequence.toString().toLower()); + entry.insert(u"command"_q, j->second); + entry.insert(u"removed"_q, true); + shortcuts.append(entry); + } + } } } @@ -669,6 +699,17 @@ void Manager::remove(const QKeySequence &keys) { } } +void Manager::remove(const QKeySequence &keys, Command command) { + const auto i = _shortcuts.find(keys); + if (i != end(_shortcuts)) { + _commandByObject.remove(i->second.get(), command); + if (!_commandByObject.contains(i->second.get())) { + unregister(std::move(i->second)); + _shortcuts.erase(i); + } + } +} + void Manager::unregister(base::unique_qptr shortcut) { if (shortcut) { _commandByObject.removeAll(shortcut.get()); From 852ab19760dffee9256c1adc43fbabd17a750517 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 18 Feb 2025 12:54:24 +0400 Subject: [PATCH 044/103] Update API scheme, track stars-per-message. --- Telegram/SourceFiles/api/api_premium.cpp | 120 +++++++++++------- Telegram/SourceFiles/api/api_premium.h | 23 ++-- .../boxes/peer_list_controllers.cpp | 43 ++++--- .../SourceFiles/boxes/peer_list_controllers.h | 16 +-- .../boxes/peers/add_participants_box.cpp | 6 +- .../boxes/peers/edit_peer_invite_link.cpp | 4 +- Telegram/SourceFiles/boxes/share_box.cpp | 51 ++++---- Telegram/SourceFiles/boxes/share_box.h | 11 +- .../calls/group/calls_group_settings.cpp | 4 +- Telegram/SourceFiles/data/data_channel.cpp | 2 - .../data/data_chat_participant_status.cpp | 4 +- Telegram/SourceFiles/data/data_peer.cpp | 2 +- .../SourceFiles/data/data_peer_values.cpp | 4 +- Telegram/SourceFiles/data/data_session.cpp | 38 ++++-- Telegram/SourceFiles/data/data_user.cpp | 30 +++-- Telegram/SourceFiles/data/data_user.h | 18 +-- .../history/history_inner_widget.cpp | 13 +- .../SourceFiles/history/history_widget.cpp | 2 +- .../controls/history_view_draft_options.cpp | 9 +- .../history/view/history_view_about_view.cpp | 4 +- .../history/view/history_view_fake_items.cpp | 3 +- Telegram/SourceFiles/main/main_account.cpp | 3 +- .../media/stories/media_stories_reply.cpp | 2 +- .../media/stories/media_stories_share.cpp | 8 +- .../SourceFiles/menu/menu_ttl_validator.cpp | 3 +- Telegram/SourceFiles/mtproto/scheme/api.tl | 12 +- .../SourceFiles/window/window_peer_menu.cpp | 11 +- 27 files changed, 256 insertions(+), 190 deletions(-) diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 3bd36b40c..26913462b 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -377,15 +377,15 @@ const Data::PremiumSubscriptionOptions &Premium::subscriptionOptions() const { return _subscriptionOptions; } -rpl::producer<> Premium::somePremiumRequiredResolved() const { - return _somePremiumRequiredResolved.events(); +rpl::producer<> Premium::someMessageMoneyRestrictionsResolved() const { + return _someMessageMoneyRestrictionsResolved.events(); } -void Premium::resolvePremiumRequired(not_null user) { - _resolvePremiumRequiredUsers.emplace(user); - if (!_premiumRequiredRequestScheduled - && _resolvePremiumRequestedUsers.empty()) { - _premiumRequiredRequestScheduled = true; +void Premium::resolveMessageMoneyRestrictions(not_null user) { + _resolveMessageMoneyRequiredUsers.emplace(user); + if (!_messageMoneyRequestScheduled + && _resolveMessageMoneyRequestedUsers.empty()) { + _messageMoneyRequestScheduled = true; crl::on_main(_session, [=] { requestPremiumRequiredSlice(); }); @@ -393,50 +393,65 @@ void Premium::resolvePremiumRequired(not_null user) { } void Premium::requestPremiumRequiredSlice() { - _premiumRequiredRequestScheduled = false; - if (!_resolvePremiumRequestedUsers.empty() - || _resolvePremiumRequiredUsers.empty()) { + _messageMoneyRequestScheduled = false; + if (!_resolveMessageMoneyRequestedUsers.empty() + || _resolveMessageMoneyRequiredUsers.empty()) { return; } constexpr auto kPerRequest = 100; - auto users = MTP_vector_from_range(_resolvePremiumRequiredUsers + auto users = MTP_vector_from_range(_resolveMessageMoneyRequiredUsers | ranges::views::transform(&UserData::inputUser)); if (users.v.size() > kPerRequest) { auto shortened = users.v; shortened.resize(kPerRequest); users = MTP_vector(std::move(shortened)); - const auto from = begin(_resolvePremiumRequiredUsers); - _resolvePremiumRequestedUsers = { from, from + kPerRequest }; - _resolvePremiumRequiredUsers.erase(from, from + kPerRequest); + const auto from = begin(_resolveMessageMoneyRequiredUsers); + _resolveMessageMoneyRequestedUsers = { from, from + kPerRequest }; + _resolveMessageMoneyRequiredUsers.erase(from, from + kPerRequest); } else { - _resolvePremiumRequestedUsers - = base::take(_resolvePremiumRequiredUsers); + _resolveMessageMoneyRequestedUsers + = base::take(_resolveMessageMoneyRequiredUsers); } - const auto finish = [=](const QVector &list) { - constexpr auto me = UserDataFlag::MeRequiresPremiumToWrite; - constexpr auto known = UserDataFlag::RequirePremiumToWriteKnown; - constexpr auto mask = me | known; + const auto finish = [=](const QVector &list) { auto index = 0; - for (const auto &user : base::take(_resolvePremiumRequestedUsers)) { - const auto require = (index < list.size()) - && mtpIsTrue(list[index++]); - user->setFlags((user->flags() & ~mask) - | known - | (require ? me : UserDataFlag())); + for (const auto &user : base::take(_resolveMessageMoneyRequestedUsers)) { + const auto set = [&](bool requirePremium, int stars) { + using Flag = UserDataFlag; + constexpr auto me = Flag::RequiresPremiumToWrite; + constexpr auto known = Flag::MessageMoneyRestrictionsKnown; + constexpr auto hasPrem = Flag::HasRequirePremiumToWrite; + constexpr auto hasStars = Flag::HasStarsPerMessage; + user->setStarsPerMessage(stars); + user->setFlags((user->flags() & ~(me | hasPrem | hasStars)) + | known + | (requirePremium ? (me | hasPrem) : Flag()) + | (stars ? hasStars : Flag())); + }; + if (index >= list.size()) { + set(false, 0); + continue; + } + list[index++].match([&](const MTPDrequirementToContactEmpty &) { + set(false, 0); + }, [&](const MTPDrequirementToContactPremium &) { + set(true, 0); + }, [&](const MTPDrequirementToContactPaidMessages &data) { + set(false, data.vstars_amount().v); + }); } - if (!_premiumRequiredRequestScheduled - && !_resolvePremiumRequiredUsers.empty()) { - _premiumRequiredRequestScheduled = true; + if (!_messageMoneyRequestScheduled + && !_resolveMessageMoneyRequiredUsers.empty()) { + _messageMoneyRequestScheduled = true; crl::on_main(_session, [=] { requestPremiumRequiredSlice(); }); } - _somePremiumRequiredResolved.fire({}); + _someMessageMoneyRestrictionsResolved.fire({}); }; _session->api().request( - MTPusers_GetIsPremiumRequiredToContact(std::move(users)) - ).done([=](const MTPVector &result) { + MTPusers_GetRequirementsToContact(std::move(users)) + ).done([=](const MTPVector &result) { finish(result.v); }).fail([=] { finish({}); @@ -694,28 +709,32 @@ rpl::producer SponsoredToggle::setToggled(bool v) { }; } -RequirePremiumState ResolveRequiresPremiumToWrite( +MessageMoneyRestriction ResolveMessageMoneyRestrictions( not_null peer, History *maybeHistory) { const auto user = peer->asUser(); - if (!user - || !user->someRequirePremiumToWrite() - || user->session().premium()) { - return RequirePremiumState::No; - } else if (user->requirePremiumToWriteKnown()) { - return user->meRequiresPremiumToWrite() - ? RequirePremiumState::Yes - : RequirePremiumState::No; + if (!user) { + return { .known = true }; + } else if (user->messageMoneyRestrictionsKnown()) { + return { + .starsPerMessage = user->starsPerMessage(), + .premiumRequired = (user->requiresPremiumToWrite() + && !user->session().premium()), + .known = true, + }; + } else if (user->hasStarsPerMessage()) { + return {}; + } else if (!user->hasRequirePremiumToWrite()) { + return { .known = true }; } else if (user->flags() & UserDataFlag::MutualContact) { - return RequirePremiumState::No; + return { .known = true }; } else if (!maybeHistory) { - return RequirePremiumState::Unknown; + return {}; } - const auto update = [&](bool require) { using Flag = UserDataFlag; - constexpr auto known = Flag::RequirePremiumToWriteKnown; - constexpr auto me = Flag::MeRequiresPremiumToWrite; + constexpr auto known = Flag::MessageMoneyRestrictionsKnown; + constexpr auto me = Flag::RequiresPremiumToWrite; user->setFlags((user->flags() & ~me) | known | (require ? me : Flag())); @@ -727,16 +746,19 @@ RequirePremiumState ResolveRequiresPremiumToWrite( const auto item = view->data(); if (!item->out() && !item->isService()) { update(false); - return RequirePremiumState::No; + return { .known = true }; } } } if (user->isContact() // Here we know, that we're not in his contacts. && maybeHistory->loadedAtTop() // And no incoming messages. && maybeHistory->loadedAtBottom()) { - update(true); + return { + .premiumRequired = !user->session().premium(), + .known = true, + }; } - return RequirePremiumState::Unknown; + return {}; } rpl::producer RandomHelloStickerValue( diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index a7757a490..0430c754b 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -116,8 +116,9 @@ public: [[nodiscard]] auto subscriptionOptions() const -> const Data::PremiumSubscriptionOptions &; - [[nodiscard]] rpl::producer<> somePremiumRequiredResolved() const; - void resolvePremiumRequired(not_null user); + [[nodiscard]] auto someMessageMoneyRestrictionsResolved() const + -> rpl::producer<>; + void resolveMessageMoneyRestrictions(not_null user); private: void reloadPromo(); @@ -166,10 +167,10 @@ private: Data::PremiumSubscriptionOptions _subscriptionOptions; - rpl::event_stream<> _somePremiumRequiredResolved; - base::flat_set> _resolvePremiumRequiredUsers; - base::flat_set> _resolvePremiumRequestedUsers; - bool _premiumRequiredRequestScheduled = false; + rpl::event_stream<> _someMessageMoneyRestrictionsResolved; + base::flat_set> _resolveMessageMoneyRequiredUsers; + base::flat_set> _resolveMessageMoneyRequestedUsers; + bool _messageMoneyRequestScheduled = false; }; @@ -244,12 +245,12 @@ private: }; -enum class RequirePremiumState { - Unknown, - Yes, - No, +struct MessageMoneyRestriction { + int starsPerMessage = 0; + bool premiumRequired = false; + bool known = false; }; -[[nodiscard]] RequirePremiumState ResolveRequiresPremiumToWrite( +[[nodiscard]] MessageMoneyRestriction ResolveMessageMoneyRestrictions( not_null peer, History *maybeHistory); diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index ff3e2722a..95105ee60 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -283,8 +283,9 @@ RecipientRow::RecipientRow( , _maybeHistory(maybeHistory) , _resolvePremiumRequired(maybeLockedSt != nullptr) { if (maybeLockedSt - && (Api::ResolveRequiresPremiumToWrite(peer, maybeHistory) - == Api::RequirePremiumState::Yes)) { + && Api::ResolveMessageMoneyRestrictions( + peer, + maybeHistory).premiumRequired) { _lockedSt = maybeLockedSt; } } @@ -305,8 +306,9 @@ bool RecipientRow::refreshLock( not_null maybeLockedSt) { if (const auto user = peer()->asUser()) { const auto locked = _resolvePremiumRequired - && (Api::ResolveRequiresPremiumToWrite(user, _maybeHistory) - == Api::RequirePremiumState::Yes); + && Api::ResolveMessageMoneyRestrictions( + user, + _maybeHistory).premiumRequired; if (this->locked() != locked) { setLocked(locked ? maybeLockedSt.get() : nullptr); return true; @@ -320,20 +322,22 @@ void RecipientRow::preloadUserpic() { if (!_resolvePremiumRequired) { return; - } else if (Api::ResolveRequiresPremiumToWrite(peer(), _maybeHistory) - == Api::RequirePremiumState::Unknown) { + } else if (!Api::ResolveMessageMoneyRestrictions( + peer(), + _maybeHistory).known) { const auto user = peer()->asUser(); - user->session().api().premium().resolvePremiumRequired(user); + user->session().api().premium().resolveMessageMoneyRestrictions( + user); } } -void TrackPremiumRequiredChanges( +void TrackMessageMoneyRestrictionsChanges( not_null controller, rpl::lifetime &lifetime) { const auto session = &controller->session(); rpl::merge( Data::AmPremiumValue(session) | rpl::to_empty, - session->api().premium().somePremiumRequiredResolved() + session->api().premium().someMessageMoneyRestrictionsResolved() ) | rpl::start_with_next([=] { const auto st = &controller->computeListSt().item; const auto delegate = controller->delegate(); @@ -726,7 +730,7 @@ std::unique_ptr ContactsBoxController::createRow( return std::make_unique(user); } -RecipientPremiumRequiredError WritePremiumRequiredError( +RecipientMoneyRestrictionError WriteMoneyRestrictionError( not_null user) { return { .text = tr::lng_send_non_premium_message_toast( @@ -759,7 +763,7 @@ ChooseRecipientBoxController::ChooseRecipientBoxController( , _session(args.session) , _callback(std::move(args.callback)) , _filter(std::move(args.filter)) -, _premiumRequiredError(std::move(args.premiumRequiredError)) { +, _moneyRestrictionError(std::move(args.moneyRestrictionError)) { } Main::Session &ChooseRecipientBoxController::session() const { @@ -769,14 +773,17 @@ Main::Session &ChooseRecipientBoxController::session() const { void ChooseRecipientBoxController::prepareViewHook() { delegate()->peerListSetTitle(tr::lng_forward_choose()); - if (_premiumRequiredError) { - TrackPremiumRequiredChanges(this, lifetime()); + if (_moneyRestrictionError) { + TrackMessageMoneyRestrictionsChanges(this, lifetime()); } } bool ChooseRecipientBoxController::showLockedError( not_null row) { - return RecipientRow::ShowLockedError(this, row, _premiumRequiredError); + return RecipientRow::ShowLockedError( + this, + row, + _moneyRestrictionError); } void ChooseRecipientBoxController::rowClicked(not_null row) { @@ -836,7 +843,7 @@ void ChooseRecipientBoxController::rowClicked(not_null row) { bool RecipientRow::ShowLockedError( not_null controller, not_null row, - Fn)> error) { + Fn)> error) { if (!static_cast(row.get())->locked()) { return false; } @@ -860,15 +867,15 @@ auto ChooseRecipientBoxController::createRow( : ((peer->isBroadcast() && !Data::CanSendAnything(peer)) || peer->isRepliesChat() || peer->isVerifyCodes() - || (peer->isUser() && (_premiumRequiredError - ? !peer->asUser()->canSendIgnoreRequirePremium() + || (peer->isUser() && (_moneyRestrictionError + ? !peer->asUser()->canSendIgnoreMoneyRestrictions() : !Data::CanSendAnything(peer)))); if (skip) { return nullptr; } auto result = std::make_unique( history, - _premiumRequiredError ? &computeListSt().item : nullptr); + _moneyRestrictionError ? &computeListSt().item : nullptr); return result; } diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.h b/Telegram/SourceFiles/boxes/peer_list_controllers.h index 07f71534a..9bec5a98e 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.h +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h @@ -93,11 +93,11 @@ private: }; -struct RecipientPremiumRequiredError { +struct RecipientMoneyRestrictionError { TextWithEntities text; }; -[[nodiscard]] RecipientPremiumRequiredError WritePremiumRequiredError( +[[nodiscard]] RecipientMoneyRestrictionError WriteMoneyRestrictionError( not_null user); class RecipientRow : public PeerListRow { @@ -112,7 +112,7 @@ public: [[nodiscard]] static bool ShowLockedError( not_null controller, not_null row, - Fn)> error); + Fn)> error); [[nodiscard]] History *maybeHistory() const { return _maybeHistory; @@ -135,7 +135,7 @@ private: }; -void TrackPremiumRequiredChanges( +void TrackMessageMoneyRestrictionsChanges( not_null controller, rpl::lifetime &lifetime); @@ -261,8 +261,8 @@ struct ChooseRecipientArgs { FnMut)> callback; Fn)> filter; - using PremiumRequiredError = RecipientPremiumRequiredError; - Fn)> premiumRequiredError; + using MoneyRestrictionError = RecipientMoneyRestrictionError; + Fn)> moneyRestrictionError; }; class ChooseRecipientBoxController @@ -290,8 +290,8 @@ private: const not_null _session; FnMut)> _callback; Fn)> _filter; - Fn)> _premiumRequiredError; + Fn)> _moneyRestrictionError; }; diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp index ab48b6b8d..c2b6ff565 100644 --- a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp @@ -584,8 +584,8 @@ void AddParticipantsBoxController::subscribeToMigration() { } void AddParticipantsBoxController::rowClicked(not_null row) { - const auto premiumRequiredError = WritePremiumRequiredError; - if (RecipientRow::ShowLockedError(this, row, premiumRequiredError)) { + const auto moneyRestrictionError = WriteMoneyRestrictionError; + if (RecipientRow::ShowLockedError(this, row, moneyRestrictionError)) { return; } const auto &serverConfig = session().serverConfig(); @@ -614,7 +614,7 @@ void AddParticipantsBoxController::itemDeselectedHook( void AddParticipantsBoxController::prepareViewHook() { updateTitle(); - TrackPremiumRequiredChanges(this, lifetime()); + TrackMessageMoneyRestrictionsChanges(this, lifetime()); } int AddParticipantsBoxController::alreadyInCount() const { diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp index 2797b5394..f37bda892 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp @@ -1530,7 +1530,7 @@ object_ptr ShareInviteLinkBox( }; auto filterCallback = [](not_null thread) { if (const auto user = thread->peer()->asUser()) { - if (user->canSendIgnoreRequirePremium()) { + if (user->canSendIgnoreMoneyRestrictions()) { return true; } } @@ -1541,7 +1541,7 @@ object_ptr ShareInviteLinkBox( .copyCallback = std::move(copyCallback), .submitCallback = std::move(submitCallback), .filterCallback = std::move(filterCallback), - .premiumRequiredError = SharePremiumRequiredError(), + .moneyRestrictionError = ShareMessageMoneyRestrictionError(), }); *box = Ui::MakeWeak(object.data()); return object; diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index c88a39f6b..ebf25e08f 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -721,11 +721,11 @@ ShareBox::Inner::Inner( _rowHeight = st::shareRowHeight; setAttribute(Qt::WA_OpaquePaintEvent); - if (_descriptor.premiumRequiredError) { + if (_descriptor.moneyRestrictionError) { const auto session = _descriptor.session; rpl::merge( Data::AmPremiumValue(session) | rpl::to_empty, - session->api().premium().somePremiumRequiredResolved() + session->api().premium().someMessageMoneyRestrictionsResolved() ) | rpl::start_with_next([=] { refreshLockedRows(); }, lifetime()); @@ -794,7 +794,7 @@ bool ShareBox::Inner::showLockedError(not_null chat) { ::Settings::ShowPremiumPromoToast( Main::MakeSessionShow(_show, _descriptor.session), ChatHelpers::ResolveWindowDefault(), - _descriptor.premiumRequiredError(chat->peer->asUser()).text, + _descriptor.moneyRestrictionError(chat->peer->asUser()).text, u"require_premium"_q); return true; } @@ -803,10 +803,9 @@ void ShareBox::Inner::refreshLockedRows() { auto changed = false; for (const auto &[peer, data] : _dataMap) { const auto history = data->history; - const auto locked = (Api::ResolveRequiresPremiumToWrite( + const auto locked = Api::ResolveMessageMoneyRestrictions( history->peer, - history - ) == Api::RequirePremiumState::Yes); + history).premiumRequired; if (data->locked != locked) { data->locked = locked; changed = true; @@ -814,10 +813,9 @@ void ShareBox::Inner::refreshLockedRows() { } for (const auto &data : d_byUsernameFiltered) { const auto history = data->history; - const auto locked = (Api::ResolveRequiresPremiumToWrite( + const auto locked = Api::ResolveMessageMoneyRestrictions( history->peer, - history - ) == Api::RequirePremiumState::Yes); + history).premiumRequired; if (data->locked != locked) { data->locked = locked; changed = true; @@ -887,12 +885,11 @@ void ShareBox::Inner::updateChatName(not_null chat) { } void ShareBox::Inner::initChatLocked(not_null chat) { - if (_descriptor.premiumRequiredError) { + if (_descriptor.moneyRestrictionError) { const auto history = chat->history; - if (Api::ResolveRequiresPremiumToWrite( - history->peer, - history - ) == Api::RequirePremiumState::Yes) { + if (Api::ResolveMessageMoneyRestrictions( + history->peer, + history).premiumRequired) { chat->locked = true; } } @@ -1019,14 +1016,14 @@ void ShareBox::Inner::loadProfilePhotos() { void ShareBox::Inner::preloadUserpic(not_null entry) { entry->chatListPreloadData(); const auto history = entry->asHistory(); - if (!_descriptor.premiumRequiredError || !history) { + if (!_descriptor.moneyRestrictionError || !history) { return; - } else if (Api::ResolveRequiresPremiumToWrite( - history->peer, - history - ) == Api::RequirePremiumState::Unknown) { + } else if (!Api::ResolveMessageMoneyRestrictions( + history->peer, + history).known) { const auto user = history->peer->asUser(); - _descriptor.session->api().premium().resolvePremiumRequired(user); + _descriptor.session->api().premium().resolveMessageMoneyRestrictions( + user); } } @@ -1707,7 +1704,7 @@ void FastShareMessage( const auto requiresInline = item->requiresSendInlineRight(); auto filterCallback = [=](not_null thread) { if (const auto user = thread->peer()->asUser()) { - if (user->canSendIgnoreRequirePremium()) { + if (user->canSendIgnoreMoneyRestrictions()) { return true; } } @@ -1733,7 +1730,7 @@ void FastShareMessage( .captionsCount = ItemsForwardCaptionsCount(items), .show = !hasOnlyForcedForwardedInfo, }, - .premiumRequiredError = SharePremiumRequiredError(), + .moneyRestrictionError = ShareMessageMoneyRestrictionError(), }), Ui::LayerOption::CloseOther); } @@ -1806,7 +1803,7 @@ void FastShareLink( }; auto filterCallback = [](not_null<::Data::Thread*> thread) { if (const auto user = thread->peer()->asUser()) { - if (user->canSendIgnoreRequirePremium()) { + if (user->canSendIgnoreMoneyRestrictions()) { return true; } } @@ -1819,13 +1816,13 @@ void FastShareLink( .submitCallback = std::move(submitCallback), .filterCallback = std::move(filterCallback), .st = st, - .premiumRequiredError = SharePremiumRequiredError(), + .moneyRestrictionError = ShareMessageMoneyRestrictionError(), }), Ui::LayerOption::KeepOther, anim::type::normal); } -auto SharePremiumRequiredError() --> Fn)> { - return WritePremiumRequiredError; +auto ShareMessageMoneyRestrictionError() +-> Fn)> { + return WriteMoneyRestrictionError; } diff --git a/Telegram/SourceFiles/boxes/share_box.h b/Telegram/SourceFiles/boxes/share_box.h index 779a60e0a..83cdbcb4f 100644 --- a/Telegram/SourceFiles/boxes/share_box.h +++ b/Telegram/SourceFiles/boxes/share_box.h @@ -87,9 +87,9 @@ void FastShareLink( const QString &url, ShareBoxStyleOverrides st = {}); -struct RecipientPremiumRequiredError; -[[nodiscard]] auto SharePremiumRequiredError() --> Fn)>; +struct RecipientMoneyRestrictionError; +[[nodiscard]] auto ShareMessageMoneyRestrictionError() +-> Fn)>; class ShareBox final : public Ui::BoxContent { public: @@ -123,8 +123,9 @@ public: bool show = false; } forwardOptions; - using PremiumRequiredError = RecipientPremiumRequiredError; - Fn)> premiumRequiredError; + using MoneyRestrictionError = RecipientMoneyRestrictionError; + Fn)> moneyRestrictionError; }; ShareBox(QWidget*, Descriptor &&descriptor); diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp index 7ae977bfd..672d6d4a1 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp @@ -178,7 +178,7 @@ object_ptr ShareInviteLinkBox( }; auto filterCallback = [](not_null thread) { if (const auto user = thread->peer()->asUser()) { - if (user->canSendIgnoreRequirePremium()) { + if (user->canSendIgnoreMoneyRestrictions()) { return true; } } @@ -199,7 +199,7 @@ object_ptr ShareInviteLinkBox( tr::lng_group_call_copy_speaker_link(), tr::lng_group_call_copy_listener_link()), .st = st.shareBox ? *st.shareBox : ShareBoxStyleOverrides(), - .premiumRequiredError = SharePremiumRequiredError(), + .moneyRestrictionError = ShareMessageMoneyRestrictionError(), }); *box = result.data(); return result; diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 909823794..e4f3dc831 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -1272,8 +1272,6 @@ void ApplyChannelUpdate( } else { channel->setLinkedChat(nullptr); } - channel->setStarsPerMessage( - update.vsend_paid_messages_stars().value_or_empty()); if (const auto history = channel->owner().historyLoaded(channel)) { if (const auto available = update.vavailable_min_id()) { history->clearUpTill(available->v); diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.cpp b/Telegram/SourceFiles/data/data_chat_participant_status.cpp index 537663a36..10ffb3847 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.cpp +++ b/Telegram/SourceFiles/data/data_chat_participant_status.cpp @@ -120,7 +120,7 @@ bool CanSendAnyOf( || user->isRepliesChat() || user->isVerifyCodes()) { return false; - } else if (user->meRequiresPremiumToWrite() + } else if (user->requiresPremiumToWrite() && !user->session().premium()) { return false; } else if (rights @@ -177,7 +177,7 @@ SendError RestrictionError( using Flag = ChatRestriction; if (const auto restricted = peer->amRestricted(restriction)) { if (const auto user = peer->asUser()) { - if (user->meRequiresPremiumToWrite() + if (user->requiresPremiumToWrite() && !user->session().premium()) { return SendError({ .text = tr::lng_restricted_send_non_premium( diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 05e07ef26..96185bfa2 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -1331,7 +1331,7 @@ Data::RestrictionCheckResult PeerData::amRestricted( } }; if (const auto user = asUser()) { - if (user->meRequiresPremiumToWrite() && !user->session().premium()) { + if (user->requiresPremiumToWrite() && !user->session().premium()) { return Result::Explicit(); } return (right == ChatRestriction::SendVoiceMessages diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp index 73d041c08..b79b1dbe8 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -224,11 +224,11 @@ inline auto DefaultRestrictionValue( | ChatRestriction::SendVideoMessages); auto allowedAny = PeerFlagsValue( user, - (UserDataFlag::Deleted | UserDataFlag::MeRequiresPremiumToWrite) + (UserDataFlag::Deleted | UserDataFlag::RequiresPremiumToWrite) ) | rpl::map([=](UserDataFlags flags) { return (flags & UserDataFlag::Deleted) ? rpl::single(false) - : !(flags & UserDataFlag::MeRequiresPremiumToWrite) + : !(flags & UserDataFlag::RequiresPremiumToWrite) ? rpl::single(true) : AmPremiumValue(&user->session()); }) | rpl::flatten_latest(); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index edfc3ec99..1114873bb 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -532,14 +532,22 @@ not_null Session::processUser(const MTPUser &data) { | Flag::BotInlineGeo | Flag::Premium | Flag::Support - | Flag::SomeRequirePremiumToWrite - | Flag::RequirePremiumToWriteKnown + | Flag::HasRequirePremiumToWrite + | Flag::HasStarsPerMessage + | Flag::MessageMoneyRestrictionsKnown | (!minimal ? Flag::Contact | Flag::MutualContact | Flag::DiscardMinPhoto | Flag::StoriesHidden : Flag()); + const auto hasRequirePremiumToWrite + = data.is_contact_require_premium(); + const auto hasStarsPerMessage + = data.vsend_paid_messages_stars().has_value(); + if (!hasStarsPerMessage) { + result->setStarsPerMessage(0); + } const auto storiesState = minimal ? std::optional() : data.is_stories_unavailable() @@ -554,14 +562,25 @@ not_null Session::processUser(const MTPUser &data) { | (data.is_bot_inline_geo() ? Flag::BotInlineGeo : Flag()) | (data.is_premium() ? Flag::Premium : Flag()) | (data.is_support() ? Flag::Support : Flag()) - | (data.is_contact_require_premium() - ? (Flag::SomeRequirePremiumToWrite - | (result->someRequirePremiumToWrite() - ? (result->requirePremiumToWriteKnown() - ? Flag::RequirePremiumToWriteKnown + | (hasRequirePremiumToWrite + ? (Flag::HasRequirePremiumToWrite + | (result->hasRequirePremiumToWrite() + ? (result->messageMoneyRestrictionsKnown() + ? Flag::MessageMoneyRestrictionsKnown : Flag()) : Flag())) : Flag()) + | (hasStarsPerMessage + ? (Flag::HasStarsPerMessage + | (result->hasStarsPerMessage() + ? (result->messageMoneyRestrictionsKnown() + ? Flag::MessageMoneyRestrictionsKnown + : Flag()) + : Flag())) + : Flag()) + | ((!hasRequirePremiumToWrite && !hasStarsPerMessage) + ? Flag::MessageMoneyRestrictionsKnown + : Flag()) | (!minimal ? (data.is_contact() ? Flag::Contact : Flag()) | (data.is_mutual_contact() ? Flag::MutualContact : Flag()) @@ -999,6 +1018,8 @@ not_null Session::processChat(const MTPChat &data) { } channel->setPhoto(data.vphoto()); + channel->setStarsPerMessage( + data.vsend_paid_messages_stars().value_or_empty()); if (wasInChannel != channel->amIn()) { flags |= UpdateFlag::ChannelAmIn; @@ -4631,7 +4652,8 @@ void Session::serviceNotification( MTPPeerColor(), // color MTPPeerColor(), // profile_color MTPint(), // bot_active_users - MTPlong())); // bot_verification_icon + MTPlong(), // bot_verification_icon + MTPlong())); // send_paid_messages_stars } const auto history = this->history(PeerData::kServiceNotificationsId); const auto insert = [=] { diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index af333ed6e..514e75b2a 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -513,19 +513,23 @@ bool UserData::hasStoriesHidden() const { return (flags() & UserDataFlag::StoriesHidden); } -bool UserData::someRequirePremiumToWrite() const { - return (flags() & UserDataFlag::SomeRequirePremiumToWrite); +bool UserData::hasRequirePremiumToWrite() const { + return (flags() & UserDataFlag::HasRequirePremiumToWrite); } -bool UserData::meRequiresPremiumToWrite() const { - return !isSelf() && (flags() & UserDataFlag::MeRequiresPremiumToWrite); +bool UserData::hasStarsPerMessage() const { + return (flags() & UserDataFlag::HasStarsPerMessage); } -bool UserData::requirePremiumToWriteKnown() const { - return (flags() & UserDataFlag::RequirePremiumToWriteKnown); +bool UserData::requiresPremiumToWrite() const { + return !isSelf() && (flags() & UserDataFlag::RequiresPremiumToWrite); } -bool UserData::canSendIgnoreRequirePremium() const { +bool UserData::messageMoneyRestrictionsKnown() const { + return (flags() & UserDataFlag::MessageMoneyRestrictionsKnown); +} + +bool UserData::canSendIgnoreMoneyRestrictions() const { return !isInaccessible() && !isRepliesChat() && !isVerifyCodes(); } @@ -732,6 +736,8 @@ void ApplyUserUpdate(not_null user, const MTPDuserFull &update) { if (const auto pinned = update.vpinned_msg_id()) { SetTopPinnedMessageId(user, pinned->v); } + user->setStarsPerMessage( + update.vsend_paid_messages_stars().value_or_empty()); using Flag = UserDataFlag; const auto mask = Flag::Blocked | Flag::HasPhoneCalls @@ -739,8 +745,8 @@ void ApplyUserUpdate(not_null user, const MTPDuserFull &update) { | Flag::CanPinMessages | Flag::VoiceMessagesForbidden | Flag::ReadDatesPrivate - | Flag::RequirePremiumToWriteKnown - | Flag::MeRequiresPremiumToWrite; + | Flag::MessageMoneyRestrictionsKnown + | Flag::RequiresPremiumToWrite; user->setFlags((user->flags() & ~mask) | (update.is_phone_calls_private() ? Flag::PhoneCallsPrivate @@ -752,9 +758,9 @@ void ApplyUserUpdate(not_null user, const MTPDuserFull &update) { ? Flag::VoiceMessagesForbidden : Flag()) | (update.is_read_dates_private() ? Flag::ReadDatesPrivate : Flag()) - | Flag::RequirePremiumToWriteKnown + | Flag::MessageMoneyRestrictionsKnown | (update.is_contact_require_premium() - ? Flag::MeRequiresPremiumToWrite + ? Flag::RequiresPremiumToWrite : Flag())); user->setIsBlocked(update.is_blocked()); user->setCallsStatus(update.is_phone_calls_private() @@ -770,8 +776,6 @@ void ApplyUserUpdate(not_null user, const MTPDuserFull &update) { user->setTranslationDisabled(update.is_translations_disabled()); user->setPrivateForwardName( update.vprivate_forward_name().value_or_empty()); - user->setStarsPerMessage( - update.vsend_paid_messages_stars().value_or_empty()); if (const auto info = user->botInfo.get()) { const auto group = update.vbot_group_admin_rights() diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index 54c647033..7bac7de75 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -110,10 +110,11 @@ enum class UserDataFlag : uint32 { StoriesHidden = (1 << 18), HasActiveStories = (1 << 19), HasUnreadStories = (1 << 20), - MeRequiresPremiumToWrite = (1 << 21), - SomeRequirePremiumToWrite = (1 << 22), - RequirePremiumToWriteKnown = (1 << 23), - ReadDatesPrivate = (1 << 24), + RequiresPremiumToWrite = (1 << 21), + HasRequirePremiumToWrite = (1 << 22), + HasStarsPerMessage = (1 << 23), + MessageMoneyRestrictionsKnown = (1 << 24), + ReadDatesPrivate = (1 << 25), }; inline constexpr bool is_flag_type(UserDataFlag) { return true; }; using UserDataFlags = base::flags; @@ -174,10 +175,11 @@ public: [[nodiscard]] bool applyMinPhoto() const; [[nodiscard]] bool hasPersonalPhoto() const; [[nodiscard]] bool hasStoriesHidden() const; - [[nodiscard]] bool someRequirePremiumToWrite() const; - [[nodiscard]] bool meRequiresPremiumToWrite() const; - [[nodiscard]] bool requirePremiumToWriteKnown() const; - [[nodiscard]] bool canSendIgnoreRequirePremium() const; + [[nodiscard]] bool hasRequirePremiumToWrite() const; + [[nodiscard]] bool hasStarsPerMessage() const; + [[nodiscard]] bool requiresPremiumToWrite() const; + [[nodiscard]] bool messageMoneyRestrictionsKnown() const; + [[nodiscard]] bool canSendIgnoreMoneyRestrictions() const; [[nodiscard]] bool readDatesPrivate() const; void setStarsPerMessage(int stars); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index c8192f37a..61ac0fa4e 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -4350,15 +4350,14 @@ void HistoryInner::refreshAboutView(bool force) { if (!info->inited) { session().api().requestFullPeer(user); } - } else if (user->meRequiresPremiumToWrite() - && !user->session().premium() - && !historyHeight()) { - refresh(); } else if (!historyHeight()) { - if (!user->isFullLoaded()) { - session().api().requestFullPeer(user); - } else { + if (user->starsPerMessage() > 0 + || (user->requiresPremiumToWrite() + && !user->session().premium()) + || user->isFullLoaded()) { refresh(); + } else { + session().api().requestFullPeer(user); } } } diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index bb8da7dfd..2334826f6 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -780,7 +780,7 @@ HistoryWidget::HistoryWidget( ) | rpl::start_with_next([=](UserData::Flags::Change change) { if (change.diff & UserData::Flag::Premium) { if (const auto user = _peer ? _peer->asUser() : nullptr) { - if (user->meRequiresPremiumToWrite()) { + if (user->requiresPremiumToWrite()) { handlePeerUpdate(); } } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp index 2d03f6dfa..549d25233 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -1199,12 +1199,15 @@ struct AuthorSelector { _peer->owner().history(_peer), &computeListSt().item)); delegate()->peerListRefreshRows(); - TrackPremiumRequiredChanges(this, _lifetime); + TrackMessageMoneyRestrictionsChanges(this, _lifetime); } void loadMoreRows() override { } void rowClicked(not_null row) override { - if (RecipientRow::ShowLockedError(this, row, WritePremiumRequiredError)) { + if (RecipientRow::ShowLockedError( + this, + row, + WriteMoneyRestrictionError)) { return; } else if (const auto onstack = _click) { onstack(); @@ -1298,7 +1301,7 @@ void ShowReplyToChatBox( .callback = [=](Chosen thread) { _singleChosen.fire_copy(thread); }, - .premiumRequiredError = WritePremiumRequiredError, + .moneyRestrictionError = WriteMoneyRestrictionError, }) { _authorRow = AuthorRowSelector( session, diff --git a/Telegram/SourceFiles/history/view/history_view_about_view.cpp b/Telegram/SourceFiles/history/view/history_view_about_view.cpp index 452f595ac..bcf978e52 100644 --- a/Telegram/SourceFiles/history/view/history_view_about_view.cpp +++ b/Telegram/SourceFiles/history/view/history_view_about_view.cpp @@ -259,7 +259,9 @@ bool AboutView::refresh() { if (user && !user->isSelf() && _history->isDisplayedEmpty()) { if (_item) { return false; - } else if (user->meRequiresPremiumToWrite() + //} else if (user->starsPerMessage() > 0) { + // setItem(makeStarsPerMessage(), nullptr); + } else if (user->requiresPremiumToWrite() && !user->session().premium()) { setItem(makePremiumRequired(), nullptr); } else if (user->isBlocked()) { diff --git a/Telegram/SourceFiles/history/view/history_view_fake_items.cpp b/Telegram/SourceFiles/history/view/history_view_fake_items.cpp index ef7b2674f..49a310aa2 100644 --- a/Telegram/SourceFiles/history/view/history_view_fake_items.cpp +++ b/Telegram/SourceFiles/history/view/history_view_fake_items.cpp @@ -61,7 +61,8 @@ PeerId GenerateUser(not_null history, const QString &name) { MTPPeerColor(), // color MTPPeerColor(), // profile_color MTPint(), // bot_active_users - MTPlong())); // bot_verification_icon + MTPlong(), // bot_verification_icon + MTPlong())); // send_paid_messages_stars return peerId; } diff --git a/Telegram/SourceFiles/main/main_account.cpp b/Telegram/SourceFiles/main/main_account.cpp index 9a4190f34..5a2f894ed 100644 --- a/Telegram/SourceFiles/main/main_account.cpp +++ b/Telegram/SourceFiles/main/main_account.cpp @@ -174,7 +174,8 @@ void Account::createSession( MTPPeerColor(), // color MTPPeerColor(), // profile_color MTPint(), // bot_active_users - MTPlong()), // bot_verification_icon + MTPlong(), // bot_verification_icon + MTPlong()), // send_paid_messages_stars serialized, streamVersion, std::move(settings)); diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index 09e222737..934d86243 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -705,7 +705,7 @@ void ReplyArea::show( using namespace HistoryView::Controls; return (can || !user - || !user->meRequiresPremiumToWrite() + || !user->requiresPremiumToWrite() || user->session().premium()) ? WriteRestriction() : WriteRestriction{ diff --git a/Telegram/SourceFiles/media/stories/media_stories_share.cpp b/Telegram/SourceFiles/media/stories/media_stories_share.cpp index 630cfacaf..37e881f38 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_share.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_share.cpp @@ -66,7 +66,7 @@ namespace Media::Stories { const auto state = std::make_shared(); auto filterCallback = [=](not_null thread) { if (const auto user = thread->peer()->asUser()) { - if (user->canSendIgnoreRequirePremium()) { + if (user->canSendIgnoreMoneyRestrictions()) { return true; } } @@ -183,7 +183,7 @@ namespace Media::Stories { .submitCallback = std::move(submitCallback), .filterCallback = std::move(filterCallback), .st = st.shareBox ? *st.shareBox : ShareBoxStyleOverrides(), - .premiumRequiredError = SharePremiumRequiredError(), + .moneyRestrictionError = ShareMessageMoneyRestrictionError(), }); } @@ -232,7 +232,7 @@ object_ptr PrepareShareAtTimeBox( const auto requiresInline = item->requiresSendInlineRight(); auto filterCallback = [=](not_null thread) { if (const auto user = thread->peer()->asUser()) { - if (user->canSendIgnoreRequirePremium()) { + if (user->canSendIgnoreMoneyRestrictions()) { return true; } } @@ -262,7 +262,7 @@ object_ptr PrepareShareAtTimeBox( .captionsCount = ItemsForwardCaptionsCount({ item }), .show = !hasOnlyForcedForwardedInfo, }, - .premiumRequiredError = SharePremiumRequiredError(), + .moneyRestrictionError = ShareMessageMoneyRestrictionError(), }); } diff --git a/Telegram/SourceFiles/menu/menu_ttl_validator.cpp b/Telegram/SourceFiles/menu/menu_ttl_validator.cpp index 1ea9d4095..9221e7907 100644 --- a/Telegram/SourceFiles/menu/menu_ttl_validator.cpp +++ b/Telegram/SourceFiles/menu/menu_ttl_validator.cpp @@ -113,7 +113,8 @@ bool TTLValidator::can() const { && !_peer->isSelf() && !_peer->isNotificationsUser() && !_peer->asUser()->isInaccessible() - && (!_peer->asUser()->meRequiresPremiumToWrite() + && !_peer->asUser()->starsPerMessage() + && (!_peer->asUser()->requiresPremiumToWrite() || _peer->session().premium())) || (_peer->isChat() && _peer->asChat()->canEditInformation() diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 8f912fbe5..452671035 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -84,7 +84,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#d3bc4b7a id:long = User; -user#4b46c37e flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true close_friend:flags2.2?true stories_hidden:flags2.3?true stories_unavailable:flags2.4?true contact_require_premium:flags2.10?true bot_business:flags2.11?true bot_has_main_app:flags2.13?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector stories_max_id:flags2.5?int color:flags2.8?PeerColor profile_color:flags2.9?PeerColor bot_active_users:flags2.12?int bot_verification_icon:flags2.14?long = User; +user#20b1422 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true close_friend:flags2.2?true stories_hidden:flags2.3?true stories_unavailable:flags2.4?true contact_require_premium:flags2.10?true bot_business:flags2.11?true bot_has_main_app:flags2.13?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector stories_max_id:flags2.5?int color:flags2.8?PeerColor profile_color:flags2.9?PeerColor bot_active_users:flags2.12?int bot_verification_icon:flags2.14?long send_paid_messages_stars:flags2.15?long = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#82d1f706 flags:# has_video:flags.0?true personal:flags.2?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; @@ -99,11 +99,11 @@ userStatusLastMonth#65899777 flags:# by_me:flags.0?true = UserStatus; chatEmpty#29562865 id:long = Chat; chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#6592a1a7 id:long title:string = Chat; -channel#e00998b7 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true signature_profiles:flags2.12?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector stories_max_id:flags2.4?int color:flags2.7?PeerColor profile_color:flags2.8?PeerColor emoji_status:flags2.9?EmojiStatus level:flags2.10?int subscription_until_date:flags2.11?int bot_verification_icon:flags2.13?long = Chat; +channel#7482147e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true signature_profiles:flags2.12?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector stories_max_id:flags2.4?int color:flags2.7?PeerColor profile_color:flags2.8?PeerColor emoji_status:flags2.9?EmojiStatus level:flags2.10?int subscription_until_date:flags2.11?int bot_verification_icon:flags2.13?long send_paid_messages_stars:flags2.14?long = Chat; channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#2633421b flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions reactions_limit:flags.20?int = ChatFull; -channelFull#67d2e29d flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int send_paid_messages_stars:flags2.20?long = ChatFull; +channelFull#52d6806b flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -1942,6 +1942,10 @@ paidReactionPrivacyPeer#dc6cfcf0 peer:InputPeer = PaidReactionPrivacy; account.paidMessagesRevenue#1e109708 stars_amount:long = account.PaidMessagesRevenue; +requirementToContactEmpty#50a9839 = RequirementToContact; +requirementToContactPremium#e581e4e9 = RequirementToContact; +requirementToContactPaidMessages#b4f67e93 stars_amount:long = RequirementToContact; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -2099,7 +2103,7 @@ account.getPaidMessagesRevenue#f1266f38 user_id:InputUser = account.PaidMessages users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#b60f5918 id:InputUser = users.UserFull; users.setSecureValueErrors#90c894b5 id:InputUser errors:Vector = Bool; -users.getIsPremiumRequiredToContact#a622aa10 id:Vector = Vector; +users.getRequirementsToContact#d89a83a3 id:Vector = Vector; contacts.getContactIDs#7adc669d hash:long = Vector; contacts.getStatuses#c4a353ee = Vector; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index f5ea985ae..655ddc6dd 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1201,7 +1201,8 @@ void Filler::addThemeEdit() { if (!user || user->isInaccessible()) { return; } - if (user->meRequiresPremiumToWrite() && !user->session().premium()) { + if ((user->requiresPremiumToWrite() && !user->session().premium()) + || user->starsPerMessage() > 0) { return; } const auto controller = _controller; @@ -1709,7 +1710,7 @@ void PeerMenuShareContactBox( ChooseRecipientArgs{ .session = &navigation->session(), .callback = std::move(callback), - .premiumRequiredError = WritePremiumRequiredError, + .moneyRestrictionError = WriteMoneyRestrictionError, }), [](not_null box) { box->addButton(tr::lng_cancel(), [=] { @@ -1924,7 +1925,7 @@ object_ptr PrepareChooseRecipientBox( .session = session, .callback = std::move(callback), .filter = filter, - .premiumRequiredError = WritePremiumRequiredError, + .moneyRestrictionError = WriteMoneyRestrictionError, }) , _selectable(selectable) { } @@ -2145,7 +2146,7 @@ QPointer ShowForwardMessagesBox( .callback = [=](Chosen thread) { _singleChosen.fire_copy(thread); }, - .premiumRequiredError = WritePremiumRequiredError, + .moneyRestrictionError = WriteMoneyRestrictionError, }) { } @@ -2555,7 +2556,7 @@ QPointer ShowShareGameBox( .session = &navigation->session(), .callback = std::move(chosen), .filter = std::move(filter), - .premiumRequiredError = WritePremiumRequiredError, + .moneyRestrictionError = WriteMoneyRestrictionError, }), std::move(initBox))); return weak->data(); From 960cf7a34bfd86b2e5f558458570a433ed54db8d Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 18 Feb 2025 19:50:55 +0400 Subject: [PATCH 045/103] Update API scheme, new paid. --- Telegram/Resources/langs/lang.strings | 2 +- Telegram/SourceFiles/api/api_common.h | 1 + Telegram/SourceFiles/api/api_polls.cpp | 7 +- Telegram/SourceFiles/api/api_polls.h | 2 +- Telegram/SourceFiles/api/api_sending.cpp | 21 +- Telegram/SourceFiles/apiwrap.cpp | 35 ++- Telegram/SourceFiles/apiwrap.h | 2 +- Telegram/SourceFiles/boxes/share_box.cpp | 7 +- Telegram/SourceFiles/data/data_channel.cpp | 42 ---- Telegram/SourceFiles/data/data_channel.h | 5 - .../data/data_chat_participant_status.cpp | 60 +++++ .../data/data_chat_participant_status.h | 20 ++ Telegram/SourceFiles/data/data_peer.cpp | 37 +-- Telegram/SourceFiles/data/data_peer.h | 5 +- Telegram/SourceFiles/data/data_user.cpp | 32 --- Telegram/SourceFiles/data/data_user.h | 5 - .../history/history_item_helpers.cpp | 49 +++- .../history/history_item_helpers.h | 1 + .../SourceFiles/history/history_widget.cpp | 219 +++++++----------- Telegram/SourceFiles/history/history_widget.h | 13 +- .../media/stories/media_stories_share.cpp | 22 +- Telegram/SourceFiles/mtproto/scheme/api.tl | 5 +- 22 files changed, 302 insertions(+), 290 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 95da224c6..ea11eb467 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3650,6 +3650,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_send_anonymous_ph" = "Send anonymously..."; "lng_story_reply_ph" = "Reply privately..."; "lng_story_comment_ph" = "Comment story..."; +"lng_message_paid_ph" = "Message for {amount}"; "lng_send_text_no" = "Text not allowed."; "lng_send_text_no_about" = "The admins of this group only allow sending {types}."; "lng_send_text_type_and_last" = "{types} and {last}"; @@ -4822,7 +4823,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_slowmode_seconds#one" = "{count} second"; "lng_slowmode_seconds#other" = "{count} seconds"; -"lng_payment_for_message" = "Pay {cost} for 1 Message"; "lng_payment_confirm_title" = "Confirm payment"; "lng_payment_confirm_text#one" = "{name} charges **{count}** Star per message."; "lng_payment_confirm_text#other" = "{name} charges **{count}** Stars per message."; diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index 77c30d095..c58f525c9 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -25,6 +25,7 @@ struct SendOptions { TimeId scheduled = 0; BusinessShortcutId shortcutId = 0; EffectId effectId = 0; + int starsApproved = 0; bool silent = false; bool handleSupportSwitch = false; bool invertCaption = false; diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp index aa03ee94d..398bd1acb 100644 --- a/Telegram/SourceFiles/api/api_polls.cpp +++ b/Telegram/SourceFiles/api/api_polls.cpp @@ -37,7 +37,7 @@ Polls::Polls(not_null api) void Polls::create( const PollData &data, - const SendAction &action, + SendAction action, Fn done, Fn fail) { _session->api().sendAction(action); @@ -59,7 +59,9 @@ void Polls::create( history->startSavingCloudDraft(topicRootId); } const auto silentPost = ShouldSendSilent(peer, action.options); - const auto starsPaid = peer->commitStarsForMessage(); + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + action.options.starsApproved); if (silentPost) { sendFlags |= MTPmessages_SendMedia::Flag::f_silent; } @@ -73,6 +75,7 @@ void Polls::create( sendFlags |= MTPmessages_SendMedia::Flag::f_effect; } if (starsPaid) { + action.options.starsApproved -= starsPaid; sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; } const auto sendAs = action.options.sendAs; diff --git a/Telegram/SourceFiles/api/api_polls.h b/Telegram/SourceFiles/api/api_polls.h index 2ff08a1ac..f77e34d67 100644 --- a/Telegram/SourceFiles/api/api_polls.h +++ b/Telegram/SourceFiles/api/api_polls.h @@ -27,7 +27,7 @@ public: void create( const PollData &data, - const SendAction &action, + SendAction action, Fn done, Fn fail); void sendVotes( diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 319032aed..83ed31a6d 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -95,8 +95,9 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { const auto messagePostAuthor = peer->isBroadcast() ? session->user()->name() : QString(); - const auto starsPaid = peer->commitStarsForMessage(); - + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + action.options.starsApproved); if (action.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; @@ -113,6 +114,7 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } if (starsPaid) { + action.options.starsApproved -= starsPaid; sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; } @@ -165,7 +167,7 @@ void SendExistingMedia( ? (*localMessageId) : session->data().nextLocalMessageId()); const auto randomId = base::RandomValue(); - const auto &action = message.action; + auto &action = message.action; auto flags = NewMessageFlags(peer); auto sendFlags = MTPmessages_SendMedia::Flags(0); @@ -195,8 +197,9 @@ void SendExistingMedia( sendFlags |= MTPmessages_SendMedia::Flag::f_entities; } const auto captionText = caption.text; - const auto starsPaid = peer->commitStarsForMessage(); - + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + action.options.starsApproved); if (action.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; @@ -213,6 +216,7 @@ void SendExistingMedia( sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } if (starsPaid) { + action.options.starsApproved -= starsPaid; sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; } @@ -351,7 +355,7 @@ bool SendDice(MessageToSend &message) { message.action.generateLocal = true; - const auto &action = message.action; + auto &action = message.action; api->sendAction(action); const auto newId = FullMsgId( @@ -390,8 +394,11 @@ bool SendDice(MessageToSend &message) { flags |= MessageFlag::InvertMedia; sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } - const auto starsPaid = peer->commitStarsForMessage(); + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + action.options.starsApproved); if (starsPaid) { + action.options.starsApproved -= starsPaid; sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; } diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 71927f5fc..778d55228 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3883,8 +3883,11 @@ void ApiWrap::sendMessage(MessageToSend &&message) { sendFlags |= MTPmessages_SendMessage::Flag::f_effect; mediaFlags |= MTPmessages_SendMedia::Flag::f_effect; } - const auto starsPaid = peer->commitStarsForMessage(); + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + action.options.starsApproved); if (starsPaid) { + action.options.starsApproved -= starsPaid; sendFlags |= MTPmessages_SendMessage::Flag::f_allow_paid_stars; mediaFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; } @@ -4020,7 +4023,7 @@ void ApiWrap::sendBotStart( void ApiWrap::sendInlineResult( not_null bot, not_null data, - const SendAction &action, + SendAction action, std::optional localMessageId, Fn done) { sendAction(action); @@ -4060,8 +4063,11 @@ void ApiWrap::sendInlineResult( if (action.options.hideViaBot) { sendFlags |= SendFlag::f_hide_via; } - const auto starsPaid = peer->commitStarsForMessage(); + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + action.options.starsApproved); if (starsPaid) { + action.options.starsApproved -= starsPaid; sendFlags |= SendFlag::f_allow_paid_stars; } @@ -4244,7 +4250,12 @@ void ApiWrap::sendMediaWithRandomId( Api::ConvertOption::SkipLocal); const auto updateRecentStickers = Api::HasAttachedStickers(media); - const auto starsPaid = peer->commitStarsForMessage(); + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + options.starsApproved); + if (starsPaid) { + options.starsApproved -= starsPaid; + } using Flag = MTPmessages_SendMedia::Flag; const auto flags = Flag(0) @@ -4304,7 +4315,7 @@ void ApiWrap::sendMultiPaidMedia( Expects(album->options.price > 0); const auto groupId = album->groupId; - const auto &options = album->options; + auto &options = album->options; const auto randomId = album->items.front().randomId; auto medias = album->items | ranges::view::transform([]( const SendingAlbum::Item &part) { @@ -4322,7 +4333,12 @@ void ApiWrap::sendMultiPaidMedia( _session, caption.entities, Api::ConvertOption::SkipLocal); - const auto starsPaid = peer->commitStarsForMessage(); + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + options.starsApproved); + if (starsPaid) { + options.starsApproved -= starsPaid; + } using Flag = MTPmessages_SendMedia::Flag; const auto flags = Flag(0) @@ -4452,7 +4468,12 @@ void ApiWrap::sendAlbumIfReady(not_null album) { const auto history = sample->history(); const auto replyTo = sample->replyTo(); const auto sendAs = album->options.sendAs; - const auto starsPaid = history->peer->commitStarsForMessage(); + const auto starsPaid = std::min( + history->peer->starsPerMessageChecked(), + album->options.starsApproved); + if (starsPaid) { + album->options.starsApproved -= starsPaid; + } using Flag = MTPmessages_SendMultiMedia::Flag; const auto flags = Flag(0) | (replyTo ? Flag::f_reply_to : Flag(0)) diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index d6e2bbc67..c9e97d0ed 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -368,7 +368,7 @@ public: void sendInlineResult( not_null bot, not_null data, - const SendAction &action, + SendAction action, std::optional localMessageId, Fn done = nullptr); void sendMessageFail( diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index ebf25e08f..3a890a91f 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -1568,7 +1568,12 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( : topicRootId; const auto peer = thread->peer(); const auto threadHistory = thread->owningHistory(); - const auto starsPaid = peer->commitStarsForMessage(); + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + options.starsApproved); + if (starsPaid) { + options.starsApproved -= starsPaid; + } histories.sendRequest(threadHistory, requestType, [=]( Fn finish) { const auto session = &threadHistory->session(); diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index e4f3dc831..410abacf9 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -880,48 +880,6 @@ void ChannelData::setStarsPerMessage(int stars) { } } -int ChannelData::starsForMessageLocked() const { - if (const auto info = mgInfo.get()) { - return info->_starsForMessageLocked; - } - return 0; -} - -void ChannelData::lockStarsForMessage() { - const auto info = mgInfo.get(); - if (!info || info->_starsForMessageLocked == info->_starsPerMessage) { - return; - } - cancelStarsForMessage(); - if (info->_starsPerMessage) { - info->_starsForMessageLocked = info->_starsPerMessage; - session().credits().lock(StarsAmount(info->_starsPerMessage)); - session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); - } -} - -int ChannelData::commitStarsForMessage() { - const auto info = mgInfo.get(); - if (!info) { - return 0; - } else if (const auto stars = base::take(info->_starsForMessageLocked)) { - session().credits().withdrawLocked(StarsAmount(stars)); - session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); - return stars; - } - return 0; -} - -void ChannelData::cancelStarsForMessage() { - const auto info = mgInfo.get(); - if (!info) { - return; - } else if (const auto stars = base::take(info->_starsForMessageLocked)) { - session().credits().unlock(StarsAmount(stars)); - session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); - } -} - int ChannelData::peerGiftsCount() const { return _peerGiftsCount; } diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 9484f8974..d9e835320 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -153,7 +153,6 @@ private: Data::ChatBotCommands _botCommands; std::unique_ptr _forum; int _starsPerMessage = 0; - int _starsForMessageLocked = 0; friend class ChannelData; @@ -464,10 +463,6 @@ public: void setStarsPerMessage(int stars); [[nodiscard]] int starsPerMessage() const; - [[nodiscard]] int starsForMessageLocked() const; - void lockStarsForMessage(); - [[nodiscard]] int commitStarsForMessage(); - void cancelStarsForMessage(); [[nodiscard]] int peerGiftsCount() const; void setPeerGiftsCount(int count); diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.cpp b/Telegram/SourceFiles/data/data_chat_participant_status.cpp index 10ffb3847..feb4349c5 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.cpp +++ b/Telegram/SourceFiles/data/data_chat_participant_status.cpp @@ -17,10 +17,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "lang/lang_keys.h" #include "main/main_session.h" +#include "storage/storage_account.h" +#include "ui/boxes/confirm_box.h" #include "ui/chat/attach/attach_prepare.h" +#include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" +#include "ui/widgets/checkbox.h" #include "window/window_session_controller.h" +#include "styles/style_widgets.h" namespace { @@ -425,4 +430,59 @@ void ShowSendErrorToast( }); } +void ShowSendPaidConfirm( + not_null navigation, + not_null peer, + SendError error, + Fn confirmed) { + return ShowSendPaidConfirm(navigation->uiShow(), peer, error, confirmed); +} + +void ShowSendPaidConfirm( + std::shared_ptr show, + not_null peer, + Data::SendError error, + Fn confirmed) { + const auto session = &peer->session(); + if (session->local().isPeerTrustedPayForMessage(peer->id)) { + confirmed(); + return; + } + //const auto messages = error.paidMessages; + const auto stars = error.paidStars; + show->showBox(Box([=](not_null box) { + const auto trust = std::make_shared>(); + const auto proceed = [=](Fn close) { + if ((*trust)->checked()) { + session->local().markPeerTrustedPayForMessage(peer->id); + } + confirmed(); + close(); + }; + Ui::ConfirmBox(box, { + .text = tr::lng_payment_confirm_text( + tr::now, + lt_count, + stars, + lt_name, + Ui::Text::Bold(peer->shortName()), + Ui::Text::RichLangValue).append(' ').append( + tr::lng_payment_confirm_sure( + tr::now, + lt_count, + stars, + Ui::Text::RichLangValue)), + .confirmed = proceed, + .confirmText = tr::lng_payment_confirm_button(), + .title = tr::lng_payment_confirm_title(), + }); + const auto skip = st::defaultCheckbox.margin.top(); + *trust = box->addRow( + object_ptr( + box, + tr::lng_payment_confirm_dont_ask(tr::now)), + st::boxRowPadding + QMargins(0, skip, 0, skip)); + })); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.h b/Telegram/SourceFiles/data/data_chat_participant_status.h index bfa24fb86..912f580fe 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.h +++ b/Telegram/SourceFiles/data/data_chat_participant_status.h @@ -189,17 +189,26 @@ struct SendError { struct Args { QString text; + int paidStars = 0; + int paidMessages = 0; int boostsToLift = 0; + bool resolving = false; bool premiumToLift = false; }; SendError(Args &&args) : text(std::move(args.text)) + , paidStars(args.paidStars) + , paidMessages(args.paidMessages) , boostsToLift(args.boostsToLift) + , resolving(args.resolving) , premiumToLift(args.premiumToLift) { } QString text; + int paidStars = 0; + int paidMessages = 0; int boostsToLift = 0; + bool resolving = false; bool premiumToLift = false; [[nodiscard]] SendError value_or(SendError other) const { @@ -244,4 +253,15 @@ void ShowSendErrorToast( not_null peer, SendError error); +void ShowSendPaidConfirm( + not_null navigation, + not_null peer, + SendError error, + Fn confirmed); +void ShowSendPaidConfirm( + std::shared_ptr show, + not_null peer, + SendError error, + Fn confirmed); + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 96185bfa2..45849d439 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -1459,38 +1459,13 @@ int PeerData::starsPerMessage() const { return 0; } -int PeerData::starsForMessageLocked() const { - if (const auto user = asUser()) { - return user->starsForMessageLocked(); - } else if (const auto channel = asChannel()) { - return channel->starsForMessageLocked(); - } - return 0; -} - -void PeerData::lockStarsForMessage() { - if (const auto user = asUser()) { - user->lockStarsForMessage(); - } else if (const auto channel = asChannel()) { - channel->lockStarsForMessage(); - } -} - -int PeerData::commitStarsForMessage() { - if (const auto user = asUser()) { - return user->commitStarsForMessage(); - } else if (const auto channel = asChannel()) { - return channel->commitStarsForMessage(); - } - return 0; -} - -void PeerData::cancelStarsForMessage() { - if (const auto user = asUser()) { - user->cancelStarsForMessage(); - } else if (const auto channel = asChannel()) { - channel->cancelStarsForMessage(); +int PeerData::starsPerMessageChecked() const { + if (const auto channel = asChannel()) { + return (channel->adminRights() || channel->amCreator()) + ? 0 + : channel->starsPerMessage(); } + return starsPerMessage(); } Data::GroupCall *PeerData::groupCall() const { diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index b67f3b5b5..8d1b09289 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -269,10 +269,7 @@ public: [[nodiscard]] bool canManageGroupCall() const; [[nodiscard]] int starsPerMessage() const; - [[nodiscard]] int starsForMessageLocked() const; - void lockStarsForMessage(); - [[nodiscard]] int commitStarsForMessage(); - void cancelStarsForMessage(); + [[nodiscard]] int starsPerMessageChecked() const; [[nodiscard]] UserData *asBot(); [[nodiscard]] const UserData *asBot() const; diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 514e75b2a..903ea87e5 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -552,38 +552,6 @@ void UserData::setStarsPerMessage(int stars) { } } -int UserData::starsForMessageLocked() const { - return _starsForMessageLocked; -} - -void UserData::lockStarsForMessage() { - if (_starsPerMessage == _starsForMessageLocked) { - return; - } - cancelStarsForMessage(); - if (_starsPerMessage) { - _starsForMessageLocked = _starsPerMessage; - session().credits().lock(StarsAmount(_starsPerMessage)); - session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); - } -} - -int UserData::commitStarsForMessage() { - if (const auto stars = base::take(_starsForMessageLocked)) { - session().credits().withdrawLocked(StarsAmount(stars)); - session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); - return stars; - } - return 0; -} - -void UserData::cancelStarsForMessage() { - if (const auto stars = base::take(_starsForMessageLocked)) { - session().credits().unlock(StarsAmount(stars)); - session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); - } -} - bool UserData::canAddContact() const { return canShareThisContact() && !isContact(); } diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index 7bac7de75..515a33313 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -184,10 +184,6 @@ public: void setStarsPerMessage(int stars); [[nodiscard]] int starsPerMessage() const; - [[nodiscard]] int starsForMessageLocked() const; - void lockStarsForMessage(); - [[nodiscard]] int commitStarsForMessage(); - void cancelStarsForMessage(); [[nodiscard]] bool canShareThisContact() const; [[nodiscard]] bool canAddContact() const; @@ -279,7 +275,6 @@ private: int _commonChatsCount = 0; int _peerGiftsCount = 0; int _starsPerMessage = 0; - int _starsForMessageLocked = 0; ContactStatus _contactStatus = ContactStatus::Unknown; CallsStatus _callsStatus = CallsStatus::Unknown; diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index d2f557c7f..e3ac2a4e7 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -93,10 +93,33 @@ Data::SendError GetErrorForSending( return tr::lng_forward_cant(tr::now); } } - if (peer->slowmodeApplied()) { - const auto count = (hasText ? 1 : 0) + const auto countMessages = [&] { + auto result = 0; + if (hasText) { + auto sending = TextWithEntities(); + auto left = TextWithEntities{ + request.text->text, + TextUtilities::ConvertTextTagsToEntities(request.text->tags) + }; + auto prepareFlags = Ui::ItemTextOptions( + thread->owningHistory(), + peer->session().user()).flags; + TextUtilities::PrepareForSending(left, prepareFlags); + + while (TextUtilities::CutPart(sending, left, MaxMessageSize)) { + ++result; + } + if (!result) { + ++result; + } + } + return result + (request.story ? 1 : 0) + + (request.mediaMessage ? 1 : 0) + (request.forward ? int(request.forward->size()) : 0); + }; + if (peer->slowmodeApplied()) { + const auto count = countMessages(); if (const auto history = peer->owner().historyLoaded(peer)) { if (!request.ignoreSlowmodeCountdown && (history->latestSendingMessage() != nullptr) @@ -135,6 +158,28 @@ Data::SendError GetErrorForSending( } } + if (const auto user = peer->asUser()) { + if (user->hasStarsPerMessage() + && !user->messageMoneyRestrictionsKnown()) { + user->updateFull(); + return Data::SendError({ .resolving = true }); + } + } else if (const auto channel = peer->asChannel()) { + if (!channel->isFullLoaded()) { + channel->updateFull(); + return Data::SendError({ .resolving = true }); + } + } + if (!peer->session().credits().loaded()) { + peer->session().credits().load(); + return Data::SendError({ .resolving = true }); + } else if (const auto perMessage = peer->starsPerMessageChecked()) { + const auto count = countMessages(); + return Data::SendError({ + .paidStars = count * perMessage, + .paidMessages = count, + }); + } return {}; } diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index 4160753bc..20d78579d 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -115,6 +115,7 @@ struct SendingErrorRequest { const Data::Story *story = nullptr; const TextWithTags *text = nullptr; bool ignoreSlowmodeCountdown = false; + bool mediaMessage = false; }; [[nodiscard]] Data::SendError GetErrorForSending( not_null peer, diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 2334826f6..6b4abe9cc 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -266,7 +266,6 @@ HistoryWidget::HistoryWidget( tr::lng_channel_mute(tr::now).toUpper(), st::historyComposeButton) , _reportMessages(this, QString(), st::historyComposeButton) -, _payForMessage(this, QString(), st::historyComposeButton) , _attachToggle(this, st::historyAttach) , _tabbedSelectorToggle(this, st::historyAttachEmoji) , _botKeyboardShow(this, st::historyBotKeyboardShow) @@ -384,7 +383,6 @@ HistoryWidget::HistoryWidget( _muteUnmute->addClickHandler([=] { toggleMuteUnmute(); }); setupGiftToChannelButton(); _reportMessages->addClickHandler([=] { reportSelectedMessages(); }); - _payForMessage->addClickHandler([=] { payForMessage(); }); _field->submits( ) | rpl::start_with_next([=](Qt::KeyboardModifiers modifiers) { sendWithModifiers(modifiers); @@ -525,7 +523,6 @@ HistoryWidget::HistoryWidget( _joinChannel->hide(); _muteUnmute->hide(); _reportMessages->hide(); - _payForMessage->hide(); initVoiceRecordBar(); @@ -816,7 +813,7 @@ HistoryWidget::HistoryWidget( const auto was = (_sendAs != nullptr); refreshSendAsToggle(); - if (was != (_sendAs != nullptr) || _peer->starsPerMessage()) { + if (was != (_sendAs != nullptr)) { updateControlsVisibility(); updateControlsGeometry(); orderWidgets(); @@ -839,8 +836,10 @@ HistoryWidget::HistoryWidget( return; } } - if ((flags & PeerUpdateFlag::BotStartToken) - || (flags & PeerUpdateFlag::StarsPerMessage)) { + if (flags & PeerUpdateFlag::StarsPerMessage) { + updateFieldPlaceholder(); + } + if (flags & PeerUpdateFlag::BotStartToken) { updateControlsVisibility(); updateControlsGeometry(); } @@ -878,7 +877,9 @@ HistoryWidget::HistoryWidget( } if (flags & PeerUpdateFlag::FullInfo) { fullInfoUpdated(); - if (const auto channel = _peer->asChannel()) { + if (_peer->starsPerMessageChecked()) { + session().credits().load(); + } else if (const auto channel = _peer->asChannel()) { if (channel->allowedReactions().paidEnabled) { session().credits().load(); } @@ -886,6 +887,15 @@ HistoryWidget::HistoryWidget( } }, lifetime()); + session().credits().loadedValue( + ) | rpl::filter( + rpl::mappers::_1 + ) | rpl::take(1) | rpl::start_with_next([=] { + if (const auto callback = base::take(_resendOnFullUpdated)) { + callback(); + } + }, lifetime()); + using Type = Data::DefaultNotify; rpl::merge( session().data().notifySettings().defaultUpdates(Type::User), @@ -1100,19 +1110,8 @@ void HistoryWidget::initVoiceRecordBar() { }, lifetime()); _voiceRecordBar->sendVoiceRequests( - ) | rpl::start_with_next([=](const auto &data) { - if (!canWriteMessage() || data.bytes.isEmpty() || !_history) { - return; - } - - auto action = prepareSendAction(data.options); - session().api().sendVoiceMessage( - data.bytes, - data.waveform, - data.duration, - data.video, - action); - _voiceRecordBar->clearListenState(); + ) | rpl::start_with_next([=](const VoiceToSend &data) { + sendVoice(data); }, lifetime()); _voiceRecordBar->cancelRequests( @@ -1965,7 +1964,6 @@ void HistoryWidget::setInnerFocus() { || isRecording() || isJoinChannel() || isBotStart() - || isPayForMessage() || isBlocked() || (!_canSendTexts && !_editMsgId)) { if (_scroll->isHidden()) { @@ -2388,10 +2386,8 @@ void HistoryWidget::showHistory( setHistory(nullptr); _list = nullptr; - if (_peer) { - _peer->cancelStarsForMessage(); - } _peer = nullptr; + _resendOnFullUpdated = nullptr; _topicsRequested.clear(); _canSendMessages = false; _canSendTexts = false; @@ -3035,7 +3031,6 @@ bool HistoryWidget::canWriteMessage() const { && !isJoinChannel() && !isMuteUnmute() && !isBotStart() - && !isPayForMessage() && !isSearching(); } @@ -3096,7 +3091,6 @@ void HistoryWidget::updateControlsVisibility() { || isJoinChannel() || isMuteUnmute() || isBotStart() - || isPayForMessage() || isReportMessages()))) { const auto toggle = [&](Ui::FlatButton *shown) { const auto toggleOne = [&](not_null button) { @@ -3108,7 +3102,6 @@ void HistoryWidget::updateControlsVisibility() { } }; toggleOne(_reportMessages); - toggleOne(_payForMessage); toggleOne(_joinChannel); toggleOne(_muteUnmute); toggleOne(_botStart); @@ -3122,8 +3115,6 @@ void HistoryWidget::updateControlsVisibility() { toggle(_reportMessages); } else if (isBlocked()) { toggle(_unblock); - } else if (isPayForMessage()) { - toggle(_payForMessage); } else if (isJoinChannel()) { toggle(_joinChannel); } else if (isMuteUnmute()) { @@ -3186,7 +3177,6 @@ void HistoryWidget::updateControlsVisibility() { _joinChannel->hide(); _muteUnmute->hide(); _reportMessages->hide(); - _payForMessage->hide(); _send->show(); updateSendButtonType(); @@ -3300,7 +3290,6 @@ void HistoryWidget::updateControlsVisibility() { _joinChannel->hide(); _muteUnmute->hide(); _reportMessages->hide(); - _payForMessage->hide(); _attachToggle->hide(); if (_silent) { _silent->hide(); @@ -4352,6 +4341,36 @@ Api::SendAction HistoryWidget::prepareSendAction( return result; } +void HistoryWidget::sendVoice(const VoiceToSend &data) { + if (!canWriteMessage() || data.bytes.isEmpty() || !_history) { + return; + } + + const auto withPaymentApproved = [=](int approved) { + auto copy = data; + copy.options.starsApproved = approved; + sendVoice(copy); + }; + const auto ignoreSlowmodeCountdown = data.options.scheduled != 0; + if (showSendMessageError( + {}, + ignoreSlowmodeCountdown, + crl::guard(this, withPaymentApproved), + data.options.starsApproved, + true)) { + return; + } + + auto action = prepareSendAction(data.options); + session().api().sendVoiceMessage( + data.bytes, + data.waveform, + data.duration, + data.video, + action); + _voiceRecordBar->clearListenState(); +} + void HistoryWidget::send(Api::SendOptions options) { if (!_history) { return; @@ -4360,9 +4379,7 @@ void HistoryWidget::send(Api::SendOptions options) { return; } else if (!options.scheduled && showSlowmodeError()) { return; - } - - if (_voiceRecordBar->isListenState()) { + } else if (_voiceRecordBar->isListenState()) { _voiceRecordBar->requestToSendWithOptions(options); return; } @@ -4376,9 +4393,16 @@ void HistoryWidget::send(Api::SendOptions options) { message.webPage = _preview->draft(); const auto ignoreSlowmodeCountdown = (options.scheduled != 0); + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + send(copy); + }; if (showSendMessageError( message.textWithTags, - ignoreSlowmodeCountdown)) { + ignoreSlowmodeCountdown, + crl::guard(this, withPaymentApproved), + options.starsApproved)) { return; } @@ -4541,53 +4565,7 @@ void HistoryWidget::reportSelectedMessages() { } } -void HistoryWidget::payForMessage() { - if (!_peer || !session().credits().loaded()) { - return; - } else if (!_peer->starsPerMessage() || _peer->starsForMessageLocked()) { - updateControlsVisibility(); - } else if (session().local().isPeerTrustedPayForMessage(_peer->id)) { - payForMessageSure(); - } else { - const auto peer = _peer; - const auto count = peer->starsPerMessage(); - controller()->show(Box([=](not_null box) { - const auto trust = std::make_shared>(); - const auto confirmed = [=](Fn close) { - payForMessageSure((*trust)->checked()); - close(); - }; - Ui::ConfirmBox(box, { - .text = tr::lng_payment_confirm_text( - tr::now, - lt_count, - count, - lt_name, - Ui::Text::Bold(peer->shortName()), - Ui::Text::RichLangValue).append(' ').append( - tr::lng_payment_confirm_sure( - tr::now, - lt_count, - count, - Ui::Text::RichLangValue)), - .confirmed = confirmed, - .confirmText = tr::lng_payment_confirm_button(), - .title = tr::lng_payment_confirm_title(), - }); - const auto skip = st::defaultCheckbox.margin.top(); - *trust = box->addRow( - object_ptr( - box, - tr::lng_payment_confirm_dont_ask(tr::now)), - st::boxRowPadding + QMargins(0, skip, 0, skip)); - })); - } -} - void HistoryWidget::payForMessageSure(bool trust) { - if (trust) { - session().local().markPeerTrustedPayForMessage(_peer->id); - } const auto required = _peer->starsPerMessage(); if (!required) { return; @@ -4595,7 +4573,6 @@ void HistoryWidget::payForMessageSure(bool trust) { const auto done = [=](Settings::SmallBalanceResult result) { if (result == Settings::SmallBalanceResult::Success || result == Settings::SmallBalanceResult::Already) { - _peer->lockStarsForMessage(); if (canWriteMessage()) { setInnerFocus(); } @@ -5154,40 +5131,6 @@ bool HistoryWidget::isSearching() const { return _composeSearch != nullptr; } -bool HistoryWidget::isPayForMessage() const { - const auto stars = _peer ? _peer->starsPerMessage() : 0; - const auto locked = _peer ? _peer->starsForMessageLocked() : 0; - if (!stars || locked) { - return false; - } else if (const auto channel = _peer->asChannel()) { - if (channel->amCreator() || channel->adminRights()) { - return false; - } - } - - const auto creating = !_payForMessageStars.current(); - _payForMessageStars = stars; - if (creating) { - session().credits().load(); - - auto text = _payForMessageStars.value( - ) | rpl::map([session = &session()](int stars) { - return tr::lng_payment_for_message( - tr::now, - lt_cost, - Ui::CreditsEmojiSmall(session).append( - Lang::FormatCountDecimal(stars)), - Ui::Text::WithEntities); - }); - Ui::SetButtonMarkedLabel( - _payForMessage, - std::move(text), - &session(), - st::historyComposeButtonText); - } - return true; -} - bool HistoryWidget::showRecordButton() const { return (_recordAvailability != Webrtc::RecordAvailability::None) && !_voiceRecordBar->isListenState() @@ -5245,7 +5188,6 @@ bool HistoryWidget::updateCmdStartShown() { && _peer->asChannel()->mgInfo->botStatus > 0))) { if (!isBotStart() && !isBlocked() - && !isPayForMessage() && !_keyboard->hasMarkup() && !_keyboard->forceReply() && !_editMsgId) { @@ -5659,7 +5601,7 @@ void HistoryWidget::moveFieldControls() { // (_botMenu.button) (_attachToggle|_replaceMedia) (_sendAs) ---- _inlineResults ------------------------------ _tabbedPanel ------ _fieldBarCancel // (_attachDocument|_attachPhoto) _field (_ttlInfo) (_scheduled) (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) _send -// (_botStart|_unblock|_joinChannel|_muteUnmute|_reportMessages|_payForMessage) +// (_botStart|_unblock|_joinChannel|_muteUnmute|_reportMessages) auto buttonsBottom = bottom - _attachToggle->height(); auto left = st::historySendRight; @@ -5733,7 +5675,6 @@ void HistoryWidget::moveFieldControls() { _joinChannel->setGeometry(fullWidthButtonRect); _muteUnmute->setGeometry(fullWidthButtonRect); _reportMessages->setGeometry(fullWidthButtonRect); - _payForMessage->setGeometry(fullWidthButtonRect); if (_sendRestriction) { _sendRestriction->setGeometry(fullWidthButtonRect); } @@ -5824,14 +5765,21 @@ void HistoryWidget::updateFieldPlaceholder() { } _field->setPlaceholder([&]() -> rpl::producer { + const auto peer = _history ? _history->peer.get() : nullptr; if (_editMsgId) { return tr::lng_edit_message_text(); - } else if (!_history) { + } else if (!peer) { return tr::lng_message_ph(); } else if ((_kbShown || _keyboard->forceReply()) && !_keyboard->placeholder().isEmpty()) { return rpl::single(_keyboard->placeholder()); - } else if (const auto channel = _history->peer->asChannel()) { + } else if (const auto stars = peer->starsPerMessageChecked()) { + return tr::lng_message_paid_ph( + lt_amount, + tr::lng_prize_credits_amount( + lt_count, + rpl::single(stars * 1.))); + } else if (const auto channel = peer->asChannel()) { const auto topic = resolveReplyToTopic(); const auto topicRootId = topic ? topic->rootId() @@ -5948,7 +5896,10 @@ Data::ForumTopic *HistoryWidget::resolveReplyToTopic() { bool HistoryWidget::showSendMessageError( const TextWithTags &textWithTags, - bool ignoreSlowmodeCountdown) { + bool ignoreSlowmodeCountdown, + Fn resend, + int starsApproved, + bool mediaMessage) { if (!_canSendMessages) { return false; } @@ -5960,8 +5911,17 @@ bool HistoryWidget::showSendMessageError( .forward = &_forwardPanel->items(), .text = &textWithTags, .ignoreSlowmodeCountdown = ignoreSlowmodeCountdown, + .mediaMessage = mediaMessage, }); - if (!error) { + if (resend && error.resolving) { + _resendOnFullUpdated = [=] { resend(starsApproved); }; + return true; + } else if (resend && error.paidStars > starsApproved) { + Data::ShowSendPaidConfirm(controller(), _peer, error, [=] { + resend(error.paidStars); + }); + return true; + } else if (!error) { return false; } Data::ShowSendErrorToast(controller(), _peer, error); @@ -6174,25 +6134,18 @@ void HistoryWidget::handleHistoryChange(not_null history) { const auto joinChannel = isJoinChannel(); const auto muteUnmute = isMuteUnmute(); const auto reportMessages = isReportMessages(); - const auto payForMessage = isPayForMessage(); const auto update = false || (_reportMessages->isHidden() == reportMessages) || (!reportMessages && _unblock->isHidden() == unblock) || (!reportMessages && !unblock - && _payForMessage->isHidden() == payForMessage) - || (!reportMessages - && !unblock - && !payForMessage && _botStart->isHidden() == botStart) || (!reportMessages && !unblock - && !payForMessage && !botStart && _joinChannel->isHidden() == joinChannel) || (!reportMessages && !unblock - && !payForMessage && !botStart && !joinChannel && _muteUnmute->isHidden() == muteUnmute); @@ -6556,7 +6509,6 @@ void HistoryWidget::updateHistoryGeometry( } else if (!editingMessage() && (isSearching() || isBlocked() - || isPayForMessage() || isBotStart() || isJoinChannel() || isMuteUnmute() @@ -6866,7 +6818,6 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { if (!isSearching() && !isBotStart() && !isBlocked() - && !isPayForMessage() && _canSendMessages && (wasVisible || (_replyTo && _replyEditMsg) @@ -8528,6 +8479,9 @@ void HistoryWidget::fullInfoUpdated() { updateControlsVisibility(); updateControlsGeometry(); } + if (const auto callback = base::take(_resendOnFullUpdated)) { + callback(); + } } void HistoryWidget::handlePeerUpdate() { @@ -8743,7 +8697,6 @@ void HistoryWidget::updateTopBarSelection() { || (_list && _list->wasSelectedText()) || isRecording() || isBotStart() - || isPayForMessage() || isBlocked() || (!_canSendTexts && !_editMsgId)) { _list->setFocus(); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index a6f61228e..0b487ea74 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -115,6 +115,7 @@ class TTLButton; class WebpageProcessor; class CharactersLimitLabel; class PhotoEditSpoilerManager; +struct VoiceToSend; } // namespace HistoryView::Controls class BotKeyboard; @@ -318,6 +319,7 @@ protected: private: using TabbedPanel = ChatHelpers::TabbedPanel; using TabbedSelector = ChatHelpers::TabbedSelector; + using VoiceToSend = HistoryView::Controls::VoiceToSend; enum ScrollChangeType { ScrollChangeNone, @@ -401,6 +403,7 @@ private: [[nodiscard]] Api::SendAction prepareSendAction( Api::SendOptions options) const; + void sendVoice(const VoiceToSend &data); void send(Api::SendOptions options); void sendWithModifiers(Qt::KeyboardModifiers modifiers); void sendScheduled(Api::SendOptions initialOptions); @@ -422,7 +425,6 @@ private: [[nodiscard]] int computeMaxFieldHeight() const; void toggleMuteUnmute(); void reportSelectedMessages(); - void payForMessage(); void payForMessageSure(bool trust = false); void showKeyboardHideButton(); void toggleKeyboard(bool manual = true); @@ -468,7 +470,10 @@ private: std::optional compress) const; bool showSendMessageError( const TextWithTags &textWithTags, - bool ignoreSlowmodeCountdown); + bool ignoreSlowmodeCountdown, + Fn resend = nullptr, + int starsApproved = 0, + bool mediaMessage = false); void sendingFilesConfirmed( Ui::PreparedList &&list, @@ -642,7 +647,6 @@ private: [[nodiscard]] bool isJoinChannel() const; [[nodiscard]] bool isMuteUnmute() const; [[nodiscard]] bool isReportMessages() const; - [[nodiscard]] bool isPayForMessage() const; bool updateCmdStartShown(); void updateSendButtonType(); [[nodiscard]] bool showRecordButton() const; @@ -761,6 +765,7 @@ private: std::unique_ptr _autocomplete; std::unique_ptr _emojiSuggestions; object_ptr _supportAutocomplete; + Fn _resendOnFullUpdated; UserData *_inlineBot = nullptr; QString _inlineBotUsername; @@ -781,8 +786,6 @@ private: QPointer _giftToChannelIn; QPointer _giftToChannelOut; object_ptr _reportMessages; - object_ptr _payForMessage; - mutable rpl::variable _payForMessageStars; struct { object_ptr button = { nullptr }; QString text; diff --git a/Telegram/SourceFiles/media/stories/media_stories_share.cpp b/Telegram/SourceFiles/media/stories/media_stories_share.cpp index 37e881f38..90f6ca1e2 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_share.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_share.cpp @@ -111,29 +111,33 @@ namespace Media::Stories { const auto threadPeer = thread->peer(); const auto threadHistory = thread->owningHistory(); const auto randomId = base::RandomValue(); - auto sendFlags = MTPmessages_SendMedia::Flags(0); + using SendFlag = MTPmessages_SendMedia::Flag; + auto sendFlags = SendFlag(0) | SendFlag(0); if (action.replyTo) { - sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to; + sendFlags |= SendFlag::f_reply_to; } const auto silentPost = ShouldSendSilent(threadPeer, options); if (silentPost) { - sendFlags |= MTPmessages_SendMedia::Flag::f_silent; + sendFlags |= SendFlag::f_silent; } if (options.scheduled) { - sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; + sendFlags |= SendFlag::f_schedule_date; } if (options.shortcutId) { - sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut; + sendFlags |= SendFlag::f_quick_reply_shortcut; } if (options.effectId) { - sendFlags |= MTPmessages_SendMedia::Flag::f_effect; + sendFlags |= SendFlag::f_effect; } if (options.invertCaption) { - sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; + sendFlags |= SendFlag::f_invert_media; } - const auto starsPaid = peer->commitStarsForMessage(); + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + options.starsApproved); if (starsPaid) { - sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; + options.starsApproved -= starsPaid; + sendFlags |= SendFlag::f_allow_paid_stars; } const auto done = [=] { if (!--state->requests) { diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 452671035..69a98e922 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -220,7 +220,7 @@ inputPeerNotifySettings#cacb6ae2 flags:# show_previews:flags.0?Bool silent:flags peerNotifySettings#99622c0c flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int ios_sound:flags.3?NotificationSound android_sound:flags.4?NotificationSound other_sound:flags.5?NotificationSound stories_muted:flags.6?Bool stories_hide_sender:flags.7?Bool stories_ios_sound:flags.8?NotificationSound stories_android_sound:flags.9?NotificationSound stories_other_sound:flags.10?NotificationSound = PeerNotifySettings; -peerSettings#a8639d72 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true business_bot_paused:flags.11?true business_bot_can_reply:flags.12?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int business_bot_id:flags.13?long business_bot_manage_url:flags.13?string charge_paid_message_stars:flags.14?long = PeerSettings; +peerSettings#d8c39ec flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true business_bot_paused:flags.11?true business_bot_can_reply:flags.12?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int business_bot_id:flags.13?long business_bot_manage_url:flags.13?string charge_paid_message_stars:flags.14?long registration_month:flags.15?string phone_country:flags.16?string location_country:flags.17?string = PeerSettings; wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper; wallPaperNoFile#e0804116 id:long flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper; @@ -1480,6 +1480,7 @@ inputInvoiceChatInviteSubscription#34e793f1 hash:string = InputInvoice; inputInvoiceStarGift#e8625e92 flags:# hide_name:flags.0?true include_upgrade:flags.2?true peer:InputPeer gift_id:long message:flags.1?TextWithEntities = InputInvoice; inputInvoiceStarGiftUpgrade#4d818d5d flags:# keep_original_details:flags.0?true stargift:InputSavedStarGift = InputInvoice; inputInvoiceStarGiftTransfer#4a5f5bd9 stargift:InputSavedStarGift to_id:InputPeer = InputInvoice; +inputInvoicePremiumGiftStars#dabab2ef flags:# user_id:InputUser months:int message:flags.0?TextWithEntities = InputInvoice; payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice; @@ -1495,7 +1496,7 @@ inputStorePaymentStarsTopup#dddd0f56 stars:long currency:string amount:long = In inputStorePaymentStarsGift#1d741ef7 user_id:InputUser stars:long currency:string amount:long = InputStorePaymentPurpose; inputStorePaymentStarsGiveaway#751f08fa flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.3?true stars:long boost_peer:InputPeer additional_peers:flags.1?Vector countries_iso2:flags.2?Vector prize_description:flags.4?string random_id:long until_date:int currency:string amount:long users:int = InputStorePaymentPurpose; -premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption; +premiumGiftOption#79c059f7 flags:# months:int currency:string amount:long bot_url:flags.1?string store_product:flags.0?string = PremiumGiftOption; paymentFormMethod#88f8f21b url:string title:string = PaymentFormMethod; From 4729e51e1412932d6e811970c013bdefe8e55b39 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 18 Feb 2025 21:06:05 +0400 Subject: [PATCH 046/103] Improve phrases for paid messages. --- Telegram/Resources/langs/lang.strings | 9 +++-- Telegram/SourceFiles/apiwrap.cpp | 14 +++++-- Telegram/SourceFiles/apiwrap.h | 2 +- .../data/data_chat_participant_status.cpp | 39 ++++++++++++++++--- 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index ea11eb467..0720de5c8 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4826,10 +4826,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_payment_confirm_title" = "Confirm payment"; "lng_payment_confirm_text#one" = "{name} charges **{count}** Star per message."; "lng_payment_confirm_text#other" = "{name} charges **{count}** Stars per message."; -"lng_payment_confirm_sure#one" = "Would you like to pay **{count}** Star to send one message?"; -"lng_payment_confirm_sure#other" = "Would you like to pay **{count}** Stars to send one message?"; +"lng_payment_confirm_amount#one" = "**{count}** Star"; +"lng_payment_confirm_amount#other" = "**{count}** Stars"; +"lng_payment_confirm_sure#one" = "Would you like to pay {amount} to send **{count}** message?"; +"lng_payment_confirm_sure#other" = "Would you like to pay {amount} to send **{count}** messages?"; "lng_payment_confirm_dont_ask" = "Don't ask me again"; -"lng_payment_confirm_button" = "Pay for 1 Message"; +"lng_payment_confirm_button#one" = "Pay for {count} Message"; +"lng_payment_confirm_button#other" = "Pay for {count} Messages"; "lng_payment_bar_text" = "{name} must pay {cost} for each message to you."; "lng_payment_bar_button" = "Remove Fee"; "lng_payment_refund_title" = "Remove Fee"; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 778d55228..f10f68988 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3298,7 +3298,7 @@ void ApiWrap::finishForwarding(const SendAction &action) { void ApiWrap::forwardMessages( Data::ResolvedForwardDraft &&draft, - const SendAction &action, + SendAction action, FnMut &&successCallback) { Expects(!draft.items.empty()); @@ -3373,9 +3373,17 @@ void ApiWrap::forwardMessages( const auto requestType = Data::Histories::RequestType::Send; const auto idsCopy = localIds; const auto scheduled = action.options.scheduled; + auto paidStars = std::min( + action.options.starsApproved, + int(ids.size() * peer->starsPerMessageChecked())); + auto oneFlags = sendFlags; + if (paidStars) { + action.options.starsApproved -= paidStars; + oneFlags |= SendFlag::f_allow_paid_stars; + } histories.sendRequest(history, requestType, [=](Fn finish) { history->sendRequestId = request(MTPmessages_ForwardMessages( - MTP_flags(sendFlags), + MTP_flags(oneFlags), forwardFrom->input, MTP_vector(ids), MTP_vector(randomIds), @@ -3385,7 +3393,7 @@ void ApiWrap::forwardMessages( (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, action.options.shortcutId), MTPint(), // video_timestamp - MTPlong() // allow_paid_stars + MTP_long(paidStars) )).done([=](const MTPUpdates &result) { if (!scheduled) { this->updates().checkForSentToScheduled(result); diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index c9e97d0ed..0817560b7 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -306,7 +306,7 @@ public: void finishForwarding(const SendAction &action); void forwardMessages( Data::ResolvedForwardDraft &&draft, - const SendAction &action, + SendAction action, FnMut &&successCallback = nullptr); void shareContact( const QString &phone, diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.cpp b/Telegram/SourceFiles/data/data_chat_participant_status.cpp index feb4349c5..14ae85c26 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.cpp +++ b/Telegram/SourceFiles/data/data_chat_participant_status.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "lang/lang_keys.h" #include "main/main_session.h" +#include "settings/settings_credits_graphics.h" #include "storage/storage_account.h" #include "ui/boxes/confirm_box.h" #include "ui/chat/attach/attach_prepare.h" @@ -443,12 +444,30 @@ void ShowSendPaidConfirm( not_null peer, Data::SendError error, Fn confirmed) { + const auto check = [=] { + const auto required = error.paidStars; + if (!required) { + return; + } + const auto done = [=](Settings::SmallBalanceResult result) { + if (result == Settings::SmallBalanceResult::Success + || result == Settings::SmallBalanceResult::Already) { + confirmed(); + } + }; + Settings::MaybeRequestBalanceIncrease( + show, + required, + Settings::SmallBalanceForMessage{ .recipientId = peer->id }, + done); + }; + const auto session = &peer->session(); if (session->local().isPeerTrustedPayForMessage(peer->id)) { - confirmed(); + check(); return; } - //const auto messages = error.paidMessages; + const auto messages = error.paidMessages; const auto stars = error.paidStars; show->showBox(Box([=](not_null box) { const auto trust = std::make_shared>(); @@ -456,24 +475,32 @@ void ShowSendPaidConfirm( if ((*trust)->checked()) { session->local().markPeerTrustedPayForMessage(peer->id); } - confirmed(); + check(); close(); }; Ui::ConfirmBox(box, { .text = tr::lng_payment_confirm_text( tr::now, lt_count, - stars, + stars / messages, lt_name, Ui::Text::Bold(peer->shortName()), Ui::Text::RichLangValue).append(' ').append( tr::lng_payment_confirm_sure( tr::now, lt_count, - stars, + messages, + lt_amount, + tr::lng_payment_confirm_amount( + tr::now, + lt_count, + stars, + Ui::Text::RichLangValue), Ui::Text::RichLangValue)), .confirmed = proceed, - .confirmText = tr::lng_payment_confirm_button(), + .confirmText = tr::lng_payment_confirm_button( + lt_count, + rpl::single(messages * 1.)), .title = tr::lng_payment_confirm_title(), }); const auto skip = st::defaultCheckbox.margin.top(); From 5b809c4fc6b12e541edaa567845876e2026e1fbc Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 19 Feb 2025 13:55:46 +0400 Subject: [PATCH 047/103] Show paid stars information above a message. --- Telegram/Resources/langs/lang.strings | 2 ++ Telegram/SourceFiles/api/api_sending.cpp | 2 ++ Telegram/SourceFiles/apiwrap.cpp | 11 +++--- Telegram/SourceFiles/history/history.cpp | 3 ++ Telegram/SourceFiles/history/history_item.cpp | 6 ++++ Telegram/SourceFiles/history/history_item.h | 3 ++ .../history/view/history_view_element.cpp | 32 ++++++++++++++--- .../history/view/history_view_element.h | 9 +++-- .../history/view/history_view_message.cpp | 36 +++++++++++++++++++ 9 files changed, 94 insertions(+), 10 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0720de5c8..411b64a13 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2170,6 +2170,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_paid_message_sent#other" = "You paid {count} Stars to send a message"; "lng_action_paid_message_got#one" = "You received {count} Star from {name}"; "lng_action_paid_message_got#other" = "You received {count} Stars from {name}"; +"lng_action_paid_message_group#one" = "{from} paid {count} Star to send a message"; +"lng_action_paid_message_group#other" = "{from} paid {count} Stars to send a message"; "lng_similar_channels_title" = "Similar channels"; "lng_similar_channels_view_all" = "View all"; diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 83ed31a6d..55daafd4d 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -229,6 +229,7 @@ void SendExistingMedia( .replyTo = action.replyTo, .date = NewMessageDate(action.options), .shortcutId = action.options.shortcutId, + .starsPaid = starsPaid, .postAuthor = NewMessagePostAuthor(action), .effectId = action.options.effectId, }, media, caption); @@ -411,6 +412,7 @@ bool SendDice(MessageToSend &message) { .replyTo = action.replyTo, .date = NewMessageDate(action.options), .shortcutId = action.options.shortcutId, + .starsPaid = starsPaid, .postAuthor = NewMessagePostAuthor(action), .effectId = action.options.effectId, }, TextWithEntities(), MTP_messageMediaDice( diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index f10f68988..f571964b3 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3373,12 +3373,12 @@ void ApiWrap::forwardMessages( const auto requestType = Data::Histories::RequestType::Send; const auto idsCopy = localIds; const auto scheduled = action.options.scheduled; - auto paidStars = std::min( + const auto starsPaid = std::min( action.options.starsApproved, int(ids.size() * peer->starsPerMessageChecked())); auto oneFlags = sendFlags; - if (paidStars) { - action.options.starsApproved -= paidStars; + if (starsPaid) { + action.options.starsApproved -= starsPaid; oneFlags |= SendFlag::f_allow_paid_stars; } histories.sendRequest(history, requestType, [=](Fn finish) { @@ -3393,7 +3393,7 @@ void ApiWrap::forwardMessages( (sendAs ? sendAs->input : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, action.options.shortcutId), MTPint(), // video_timestamp - MTP_long(paidStars) + MTP_long(starsPaid) )).done([=](const MTPUpdates &result) { if (!scheduled) { this->updates().checkForSentToScheduled(result); @@ -3438,6 +3438,7 @@ void ApiWrap::forwardMessages( .replyTo = { .topicRootId = topMsgId }, .date = NewMessageDate(action.options), .shortcutId = action.options.shortcutId, + .starsPaid = action.options.starsApproved, .postAuthor = NewMessagePostAuthor(action), // forwarded messages don't have effects @@ -3906,6 +3907,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { .replyTo = action.replyTo, .date = NewMessageDate(action.options), .shortcutId = action.options.shortcutId, + .starsPaid = starsPaid, .postAuthor = NewMessagePostAuthor(action), .effectId = action.options.effectId, }, sending, media); @@ -4092,6 +4094,7 @@ void ApiWrap::sendInlineResult( .replyTo = action.replyTo, .date = NewMessageDate(action.options), .shortcutId = action.options.shortcutId, + .starsPaid = starsPaid, .viaBotId = ((bot && !action.options.hideViaBot) ? peerToUser(bot->id) : UserId()), diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 7393dee65..a62fa7b48 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -460,6 +460,9 @@ not_null History::createItem( }); if (newMessage && result->out() && result->isRegular()) { session().topPeers().increment(peer, result->date()); + if (result->starsPaid()) { + session().credits().load(true); + } } return result; } diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 8bd1c4f72..0798c1699 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -401,6 +401,7 @@ HistoryItem::HistoryItem( .from = data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0), .date = data.vdate().v, .shortcutId = data.vquick_reply_shortcut_id().value_or_empty(), + .starsPaid = int(data.vpaid_message_stars().value_or_empty()), .effectId = data.veffect().value_or_empty(), }) { _boostsApplied = data.vfrom_boosts_applied().value_or_empty(); @@ -745,6 +746,7 @@ HistoryItem::HistoryItem( : history->peer) , _flags(FinalizeMessageFlags(history, fields.flags)) , _date(fields.date) +, _starsPaid(fields.starsPaid) , _shortcutId(fields.shortcutId) , _effectId(fields.effectId) { Expects(!_shortcutId @@ -793,6 +795,10 @@ TimeId HistoryItem::date() const { return _date; } +int HistoryItem::starsPaid() const { + return _starsPaid; +} + bool HistoryItem::awaitingVideoProcessing() const { return (_flags & MessageFlag::EstimatedDate); } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 7ce520fc1..8649400bc 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -103,6 +103,7 @@ struct HistoryItemCommonFields { FullReplyTo replyTo; TimeId date = 0; BusinessShortcutId shortcutId = 0; + int starsPaid = 0; UserId viaBotId = 0; QString postAuthor; uint64 groupedId = 0; @@ -548,6 +549,7 @@ public: // content uses the color of the original sender. [[nodiscard]] PeerData *contentColorsFrom() const; [[nodiscard]] uint8 contentColorIndex() const; + [[nodiscard]] int starsPaid() const; [[nodiscard]] std::unique_ptr createView( not_null delegate, @@ -682,6 +684,7 @@ private: TimeId _date = 0; TimeId _ttlDestroyAt = 0; int _boostsApplied = 0; + int _starsPaid = 0; BusinessShortcutId _shortcutId = 0; MessageGroupId _groupId = MessageGroupId(); diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index ef12b365f..4dddabdfb 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -472,12 +472,15 @@ void DateBadge::paint( ServiceMessagePainter::PaintDate(p, st, text, width, y, w, chatWide); } -void ServicePreMessage::init(TextWithEntities string) { +void ServicePreMessage::init(PreparedServiceText string) { text = Ui::Text::String( st::serviceTextStyle, - string, + string.text, kMarkupTextOptions, st::msgMinWidth); + for (auto i = 0; i != int(string.links.size()); ++i) { + text.setLink(i + 1, string.links[i]); + } } int ServicePreMessage::resizeToWidth(int newWidth, bool chatWide) { @@ -547,6 +550,27 @@ void ServicePreMessage::paint( p.translate(0, -top); } +ClickHandlerPtr ServicePreMessage::textState( + QPoint point, + const StateRequest &request, + QRect g) const { + const auto top = g.top() - height - st::msgMargin.top(); + const auto rect = QRect(0, top, width, height) + - st::msgServiceMargin; + const auto trect = rect - st::msgServicePadding; + if (trect.contains(point)) { + auto textRequest = request.forText(); + textRequest.align = style::al_center; + return text.getState( + point - trect.topLeft(), + trect.width(), + textRequest).link; + } + return {}; +} + + + void FakeBotAboutTop::init() { if (!text.isEmpty()) { return; @@ -1411,8 +1435,8 @@ void Element::setDisplayDate(bool displayDate) { } } -void Element::setServicePreMessage(TextWithEntities text) { - if (!text.empty()) { +void Element::setServicePreMessage(PreparedServiceText text) { + if (!text.text.empty()) { AddComponents(ServicePreMessage::Bit()); const auto service = Get(); service->init(std::move(text)); diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index abb1b3ada..de00c0f48 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -16,6 +16,7 @@ class History; class HistoryBlock; class HistoryItem; struct HistoryMessageReply; +struct PreparedServiceText; namespace Data { struct Reaction; @@ -260,7 +261,7 @@ struct DateBadge : public RuntimeComponent { // displaying some text in layout of a service message above the message. struct ServicePreMessage : public RuntimeComponent { - void init(TextWithEntities string); + void init(PreparedServiceText string); int resizeToWidth(int newWidth, bool chatWide); @@ -269,6 +270,10 @@ struct ServicePreMessage const PaintContext &context, QRect g, bool chatWide) const; + [[nodiscard]] ClickHandlerPtr textState( + QPoint point, + const StateRequest &request, + QRect g) const; Ui::Text::String text; int width = 0; @@ -403,7 +408,7 @@ public: // For blocks context this should be called only from recountDisplayDate(). void setDisplayDate(bool displayDate); - void setServicePreMessage(TextWithEntities text); + void setServicePreMessage(PreparedServiceText text); bool computeIsAttachToPrevious(not_null previous); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index ce8bdb90a..1dc5c6cae 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -431,6 +431,35 @@ Message::Message( _rightAction->second->link = ReportSponsoredClickHandler(data); } } + if (const auto stars = data->starsPaid()) { + auto text = PreparedServiceText{ + .text = data->out() + ? tr::lng_action_paid_message_sent( + tr::now, + lt_count, + stars, + Ui::Text::WithEntities) + : history()->peer->isUser() + ? tr::lng_action_paid_message_got( + tr::now, + lt_count, + stars, + lt_name, + Ui::Text::Link(data->from()->shortName(), 1), + Ui::Text::WithEntities) + : tr::lng_action_paid_message_group( + tr::now, + lt_count, + stars, + lt_from, + Ui::Text::Link(data->from()->shortName(), 1), + Ui::Text::WithEntities), + }; + if (!data->out()) { + text.links.push_back(data->from()->createOpenLink()); + } + setServicePreMessage(std::move(text)); + } } Message::~Message() { @@ -2448,6 +2477,13 @@ TextState Message::textState( return result; } + if (const auto service = Get()) { + result.link = service->textState(point, request, g); + if (result.link) { + return result; + } + } + const auto bubble = drawBubble(); const auto reactionsInBubble = _reactions && embedReactionsInBubble(); const auto mediaDisplayed = media && media->isDisplayed(); From fe9bac096b962388c492532f7159bad4494007b2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 19 Feb 2025 13:59:04 +0400 Subject: [PATCH 048/103] Refresh balance after paid message sending. --- Telegram/SourceFiles/history/history_item.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 0798c1699..08afc9a2e 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -2266,6 +2266,10 @@ void HistoryItem::setRealId(MsgId newId) { if (const auto reply = Get()) { incrementReplyToTopCounter(); } + + if (out() && starsPaid()) { + _history->session().credits().load(true); + } } bool HistoryItem::canPin() const { From c38982d286b8aa35fc3e971208a45a305ae7e143 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 19 Feb 2025 15:03:11 +0400 Subject: [PATCH 049/103] Simplify paid stars check. --- Telegram/SourceFiles/api/api_sending.cpp | 1 + Telegram/SourceFiles/apiwrap.cpp | 1 + .../data/data_chat_participant_status.h | 9 -- .../history/history_item_helpers.cpp | 107 ++++++++++-------- .../history/history_item_helpers.h | 13 ++- .../SourceFiles/history/history_widget.cpp | 54 +++++---- Telegram/SourceFiles/history/history_widget.h | 4 + 7 files changed, 107 insertions(+), 82 deletions(-) diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 55daafd4d..5fe1a2df4 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -634,6 +634,7 @@ void SendConfirmedFile( .replyTo = file->to.replyTo, .date = NewMessageDate(file->to.options), .shortcutId = file->to.options.shortcutId, + .starsPaid = file->to.options.starsApproved, .postAuthor = NewMessagePostAuthor(action), .groupedId = groupId, .effectId = file->to.options.effectId, diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index f571964b3..1509fd8de 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3532,6 +3532,7 @@ void ApiWrap::sendSharedContact( .replyTo = action.replyTo, .date = NewMessageDate(action.options), .shortcutId = action.options.shortcutId, + .starsPaid = action.options.starsApproved, .postAuthor = NewMessagePostAuthor(action), .effectId = action.options.effectId, }, TextWithEntities(), MTP_messageMediaContact( diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.h b/Telegram/SourceFiles/data/data_chat_participant_status.h index 912f580fe..8de7df333 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.h +++ b/Telegram/SourceFiles/data/data_chat_participant_status.h @@ -189,26 +189,17 @@ struct SendError { struct Args { QString text; - int paidStars = 0; - int paidMessages = 0; int boostsToLift = 0; - bool resolving = false; bool premiumToLift = false; }; SendError(Args &&args) : text(std::move(args.text)) - , paidStars(args.paidStars) - , paidMessages(args.paidMessages) , boostsToLift(args.boostsToLift) - , resolving(args.resolving) , premiumToLift(args.premiumToLift) { } QString text; - int paidStars = 0; - int paidMessages = 0; int boostsToLift = 0; - bool resolving = false; bool premiumToLift = false; [[nodiscard]] SendError value_or(SendError other) const { diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index e3ac2a4e7..5e8c8d779 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -60,6 +60,33 @@ bool PeerCallKnown(not_null peer) { } // namespace +int ComputeSendingMessagesCount( + not_null history, + const SendingErrorRequest &request) { + auto result = 0; + if (request.text && !request.text->empty()) { + auto sending = TextWithEntities(); + auto left = TextWithEntities{ + request.text->text, + TextUtilities::ConvertTextTagsToEntities(request.text->tags) + }; + auto prepareFlags = Ui::ItemTextOptions( + history, + history->session().user()).flags; + TextUtilities::PrepareForSending(left, prepareFlags); + + while (TextUtilities::CutPart(sending, left, MaxMessageSize)) { + ++result; + } + if (!result) { + ++result; + } + } + return result + + (request.story ? 1 : 0) + + (request.forward ? int(request.forward->size()) : 0); +} + Data::SendError GetErrorForSending( not_null peer, SendingErrorRequest request) { @@ -93,33 +120,10 @@ Data::SendError GetErrorForSending( return tr::lng_forward_cant(tr::now); } } - const auto countMessages = [&] { - auto result = 0; - if (hasText) { - auto sending = TextWithEntities(); - auto left = TextWithEntities{ - request.text->text, - TextUtilities::ConvertTextTagsToEntities(request.text->tags) - }; - auto prepareFlags = Ui::ItemTextOptions( - thread->owningHistory(), - peer->session().user()).flags; - TextUtilities::PrepareForSending(left, prepareFlags); - - while (TextUtilities::CutPart(sending, left, MaxMessageSize)) { - ++result; - } - if (!result) { - ++result; - } - } - return result - + (request.story ? 1 : 0) - + (request.mediaMessage ? 1 : 0) - + (request.forward ? int(request.forward->size()) : 0); - }; if (peer->slowmodeApplied()) { - const auto count = countMessages(); + const auto count = request.messagesCount + ? request.messagesCount + : ComputeSendingMessagesCount(thread->owningHistory(), request); if (const auto history = peer->owner().historyLoaded(peer)) { if (!request.ignoreSlowmodeCountdown && (history->latestSendingMessage() != nullptr) @@ -157,29 +161,6 @@ Data::SendError GetErrorForSending( Ui::FormatDurationWordsSlowmode(left)); } } - - if (const auto user = peer->asUser()) { - if (user->hasStarsPerMessage() - && !user->messageMoneyRestrictionsKnown()) { - user->updateFull(); - return Data::SendError({ .resolving = true }); - } - } else if (const auto channel = peer->asChannel()) { - if (!channel->isFullLoaded()) { - channel->updateFull(); - return Data::SendError({ .resolving = true }); - } - } - if (!peer->session().credits().loaded()) { - peer->session().credits().load(); - return Data::SendError({ .resolving = true }); - } else if (const auto perMessage = peer->starsPerMessageChecked()) { - const auto count = countMessages(); - return Data::SendError({ - .paidStars = count * perMessage, - .paidMessages = count, - }); - } return {}; } @@ -201,6 +182,34 @@ Data::SendErrorWithThread GetErrorForSending( } return {}; } + +std::optional ComputePaymentDetails( + not_null peer, + int messagesCount) { + if (const auto user = peer->asUser()) { + if (user->hasStarsPerMessage() + && !user->messageMoneyRestrictionsKnown()) { + user->updateFull(); + return {}; + } + } else if (const auto channel = peer->asChannel()) { + if (!channel->isFullLoaded()) { + channel->updateFull(); + return {}; + } + } + if (!peer->session().credits().loaded()) { + peer->session().credits().load(); + return {}; + } else if (const auto perMessage = peer->starsPerMessageChecked()) { + return SendPaymentDetails{ + .messages = messagesCount, + .stars = messagesCount * perMessage, + }; + } + return SendPaymentDetails(); +} + object_ptr MakeSendErrorBox( const Data::SendErrorWithThread &error, bool withTitle) { diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index 20d78579d..e462d3bd9 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -114,9 +114,12 @@ struct SendingErrorRequest { const HistoryItemsList *forward = nullptr; const Data::Story *story = nullptr; const TextWithTags *text = nullptr; + int messagesCount = 0; bool ignoreSlowmodeCountdown = false; - bool mediaMessage = false; }; +[[nodiscard]] int ComputeSendingMessagesCount( + not_null history, + const SendingErrorRequest &request); [[nodiscard]] Data::SendError GetErrorForSending( not_null peer, SendingErrorRequest request); @@ -124,6 +127,14 @@ struct SendingErrorRequest { not_null thread, SendingErrorRequest request); +struct SendPaymentDetails { + int messages = 0; + int stars = 0; +}; +[[nodiscard]] std::optional ComputePaymentDetails( + not_null peer, + int messagesCount); + [[nodiscard]] Data::SendErrorWithThread GetErrorForSending( const std::vector> &threads, SendingErrorRequest request); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 6b4abe9cc..f2095aac4 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -4351,13 +4351,11 @@ void HistoryWidget::sendVoice(const VoiceToSend &data) { copy.options.starsApproved = approved; sendVoice(copy); }; - const auto ignoreSlowmodeCountdown = data.options.scheduled != 0; - if (showSendMessageError( - {}, - ignoreSlowmodeCountdown, - crl::guard(this, withPaymentApproved), - data.options.starsApproved, - true)) { + const auto checked = checkSendPayment( + 1 + int(_forwardPanel->items().size()), + data.options.starsApproved, + withPaymentApproved); + if (!checked) { return; } @@ -5904,28 +5902,38 @@ bool HistoryWidget::showSendMessageError( return false; } const auto topicRootId = resolveReplyToTopicRootId(); - const auto error = GetErrorForSending( - _peer, - { - .topicRootId = topicRootId, - .forward = &_forwardPanel->items(), - .text = &textWithTags, - .ignoreSlowmodeCountdown = ignoreSlowmodeCountdown, - .mediaMessage = mediaMessage, - }); - if (resend && error.resolving) { + auto request = SendingErrorRequest{ + .topicRootId = topicRootId, + .forward = &_forwardPanel->items(), + .text = &textWithTags, + .ignoreSlowmodeCountdown = ignoreSlowmodeCountdown, + }; + request.messagesCount = ComputeSendingMessagesCount(_history, request) + + (mediaMessage ? 1 : 0); + const auto error = GetErrorForSending(_peer, request); + if (error) { + Data::ShowSendErrorToast(controller(), _peer, error); + return true; + } + return resend + && !checkSendPayment(request.messagesCount, starsApproved, resend); +} + +bool HistoryWidget::checkSendPayment( + int messagesCount, + int starsApproved, + Fn resend) { + const auto details = ComputePaymentDetails(_peer, messagesCount); + if (!details) { _resendOnFullUpdated = [=] { resend(starsApproved); }; return true; - } else if (resend && error.paidStars > starsApproved) { + } else if (const auto stars = details->stars) { Data::ShowSendPaidConfirm(controller(), _peer, error, [=] { - resend(error.paidStars); + resend(stars); }); return true; - } else if (!error) { - return false; } - Data::ShowSendErrorToast(controller(), _peer, error); - return true; + return false; } bool HistoryWidget::confirmSendingFiles(const QStringList &files) { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 0b487ea74..96abc7e1e 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -474,6 +474,10 @@ private: Fn resend = nullptr, int starsApproved = 0, bool mediaMessage = false); + bool checkSendPayment( + int messagesCount, + int starsApproved, + Fn resend); void sendingFilesConfirmed( Ui::PreparedList &&list, From f74ba95e95507c0bfd8532c370e943d79d8e01ca Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Feb 2025 11:10:38 +0400 Subject: [PATCH 050/103] Don't track caption in fullscreen video view. Fixes #28981. --- Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index f50654f20..289376216 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -1510,6 +1510,9 @@ void OverlayWidget::refreshCaptionGeometry() { if (_caption.isEmpty() && (!_stories || !_stories->repost())) { _captionRect = QRect(); return; + } else if (_fullScreenVideo) { + _captionRect = QRect(); + return; } if (_groupThumbs && _groupThumbs->hiding()) { From e302f328f7f73a041b46715521396df74c75df09 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Feb 2025 12:04:24 +0400 Subject: [PATCH 051/103] Don't cut confirming emoji status. --- .../SourceFiles/info/bot/starref/info_bot_starref_common.cpp | 2 +- Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp | 1 + Telegram/SourceFiles/ui/chat/chat.style | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp index 049b84e09..acc7fa153 100644 --- a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp +++ b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp @@ -916,7 +916,7 @@ std::unique_ptr MakePeerBubbleButton( userpic->moveToLeft(left, 0, outer.width()); if (right) { right->moveToLeft( - left + *width - padding.right() - right->width(), + left + *width - padding.right() - rwidth, padding.top(), outer.width()); } diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 6cee40d89..2e7ef597f 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -465,6 +465,7 @@ std::unique_ptr MakeEmojiSetStatusPreview( st::defaultPopupMenu, makeContext), style::margins(st::normalFont->spacew, 0, 0, 0)); + emoji->entity()->resizeToWidth(emoji->entity()->textMaxWidth()); auto result = Info::BotStarRef::MakePeerBubbleButton( parent, diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 9099a97c3..fefee68f3 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1188,6 +1188,7 @@ botEmojiStatusName: FlatLabel(defaultFlatLabel) { maxHeight: 20px; } botEmojiStatusEmoji: FlatLabel(botEmojiStatusName) { + margin: margins(4px, 4px, 4px, 4px); textFg: profileVerifiedCheckBg; } From 3633c1920812b951bc7799795b549d893bc920b5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Feb 2025 12:22:20 +0400 Subject: [PATCH 052/103] Update API scheme, improve service messages. --- Telegram/Resources/langs/lang.strings | 11 ++- Telegram/SourceFiles/api/api_sending.cpp | 4 +- Telegram/SourceFiles/apiwrap.cpp | 2 +- .../data/data_chat_participant_status.cpp | 84 ------------------ .../data/data_chat_participant_status.h | 11 --- .../history/history_item_helpers.cpp | 88 +++++++++++++++++++ .../history/history_item_helpers.h | 19 ++++ .../SourceFiles/history/history_widget.cpp | 71 ++++++++++----- Telegram/SourceFiles/history/history_widget.h | 5 +- .../history/view/history_view_message.cpp | 85 ++++++++++++------ .../history/view/history_view_message.h | 1 + .../history/view/media/history_view_media.h | 13 +++ .../view/media/history_view_media_grouped.cpp | 9 ++ .../view/media/history_view_media_grouped.h | 1 + Telegram/SourceFiles/mtproto/scheme/api.tl | 4 +- 15 files changed, 253 insertions(+), 155 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 411b64a13..5b378b5d1 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2166,12 +2166,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_boost_apply#other" = "{from} boosted the group {count} times"; "lng_action_set_chat_intro" = "{from} added the message below for all empty chats. How?"; "lng_action_payment_refunded" = "{peer} refunded {amount}"; -"lng_action_paid_message_sent#one" = "You paid {count} Star to send a message"; -"lng_action_paid_message_sent#other" = "You paid {count} Stars to send a message"; +"lng_action_paid_message_sent#one" = "You paid {count} Star to {action}"; +"lng_action_paid_message_sent#other" = "You paid {count} Star to {action}"; +"lng_action_paid_message_group#one" = "{from} paid {count} Star to {action}"; +"lng_action_paid_message_group#other" = "{from} paid {count} Star to {action}"; +"lng_action_paid_message_one" = "send a message"; +"lng_action_paid_message_some#one" = "send {count} message"; +"lng_action_paid_message_some#other" = "send {count} messages"; "lng_action_paid_message_got#one" = "You received {count} Star from {name}"; "lng_action_paid_message_got#other" = "You received {count} Stars from {name}"; -"lng_action_paid_message_group#one" = "{from} paid {count} Star to send a message"; -"lng_action_paid_message_group#other" = "{from} paid {count} Stars to send a message"; "lng_similar_channels_title" = "Similar channels"; "lng_similar_channels_view_all" = "View all"; diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 5fe1a2df4..814b0a983 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -634,7 +634,9 @@ void SendConfirmedFile( .replyTo = file->to.replyTo, .date = NewMessageDate(file->to.options), .shortcutId = file->to.options.shortcutId, - .starsPaid = file->to.options.starsApproved, + .starsPaid = std::min( + history->peer->starsPerMessageChecked(), + file->to.options.starsApproved), .postAuthor = NewMessagePostAuthor(action), .groupedId = groupId, .effectId = file->to.options.effectId, diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 1509fd8de..dad5a8730 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -4481,7 +4481,7 @@ void ApiWrap::sendAlbumIfReady(not_null album) { const auto replyTo = sample->replyTo(); const auto sendAs = album->options.sendAs; const auto starsPaid = std::min( - history->peer->starsPerMessageChecked(), + history->peer->starsPerMessageChecked() * int(medias.size()), album->options.starsApproved); if (starsPaid) { album->options.starsApproved -= starsPaid; diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.cpp b/Telegram/SourceFiles/data/data_chat_participant_status.cpp index 14ae85c26..29b9cb451 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.cpp +++ b/Telegram/SourceFiles/data/data_chat_participant_status.cpp @@ -17,14 +17,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "lang/lang_keys.h" #include "main/main_session.h" -#include "settings/settings_credits_graphics.h" -#include "storage/storage_account.h" #include "ui/boxes/confirm_box.h" #include "ui/chat/attach/attach_prepare.h" #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" -#include "ui/widgets/checkbox.h" #include "window/window_session_controller.h" #include "styles/style_widgets.h" @@ -431,85 +428,4 @@ void ShowSendErrorToast( }); } -void ShowSendPaidConfirm( - not_null navigation, - not_null peer, - SendError error, - Fn confirmed) { - return ShowSendPaidConfirm(navigation->uiShow(), peer, error, confirmed); -} - -void ShowSendPaidConfirm( - std::shared_ptr show, - not_null peer, - Data::SendError error, - Fn confirmed) { - const auto check = [=] { - const auto required = error.paidStars; - if (!required) { - return; - } - const auto done = [=](Settings::SmallBalanceResult result) { - if (result == Settings::SmallBalanceResult::Success - || result == Settings::SmallBalanceResult::Already) { - confirmed(); - } - }; - Settings::MaybeRequestBalanceIncrease( - show, - required, - Settings::SmallBalanceForMessage{ .recipientId = peer->id }, - done); - }; - - const auto session = &peer->session(); - if (session->local().isPeerTrustedPayForMessage(peer->id)) { - check(); - return; - } - const auto messages = error.paidMessages; - const auto stars = error.paidStars; - show->showBox(Box([=](not_null box) { - const auto trust = std::make_shared>(); - const auto proceed = [=](Fn close) { - if ((*trust)->checked()) { - session->local().markPeerTrustedPayForMessage(peer->id); - } - check(); - close(); - }; - Ui::ConfirmBox(box, { - .text = tr::lng_payment_confirm_text( - tr::now, - lt_count, - stars / messages, - lt_name, - Ui::Text::Bold(peer->shortName()), - Ui::Text::RichLangValue).append(' ').append( - tr::lng_payment_confirm_sure( - tr::now, - lt_count, - messages, - lt_amount, - tr::lng_payment_confirm_amount( - tr::now, - lt_count, - stars, - Ui::Text::RichLangValue), - Ui::Text::RichLangValue)), - .confirmed = proceed, - .confirmText = tr::lng_payment_confirm_button( - lt_count, - rpl::single(messages * 1.)), - .title = tr::lng_payment_confirm_title(), - }); - const auto skip = st::defaultCheckbox.margin.top(); - *trust = box->addRow( - object_ptr( - box, - tr::lng_payment_confirm_dont_ask(tr::now)), - st::boxRowPadding + QMargins(0, skip, 0, skip)); - })); -} - } // namespace Data diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.h b/Telegram/SourceFiles/data/data_chat_participant_status.h index 8de7df333..bfa24fb86 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.h +++ b/Telegram/SourceFiles/data/data_chat_participant_status.h @@ -244,15 +244,4 @@ void ShowSendErrorToast( not_null peer, SendError error); -void ShowSendPaidConfirm( - not_null navigation, - not_null peer, - SendError error, - Fn confirmed); -void ShowSendPaidConfirm( - std::shared_ptr show, - not_null peer, - SendError error, - Fn confirmed); - } // namespace Data diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 5e8c8d779..2420807c0 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -38,10 +38,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "core/application.h" #include "core/click_handler_types.h" // ClickHandlerContext. +#include "settings/settings_credits_graphics.h" +#include "storage/storage_account.h" #include "ui/boxes/confirm_box.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" +#include "ui/widgets/checkbox.h" #include "ui/item_text_options.h" #include "lang/lang_keys.h" @@ -242,6 +245,91 @@ object_ptr MakeSendErrorBox( }); } +void ShowSendPaidConfirm( + not_null navigation, + not_null peer, + SendPaymentDetails details, + Fn confirmed) { + return ShowSendPaidConfirm( + navigation->uiShow(), + peer, + details, + confirmed); +} + +void ShowSendPaidConfirm( + std::shared_ptr show, + not_null peer, + SendPaymentDetails details, + Fn confirmed) { + const auto check = [=] { + const auto required = details.stars; + if (!required) { + return; + } + const auto done = [=](Settings::SmallBalanceResult result) { + if (result == Settings::SmallBalanceResult::Success + || result == Settings::SmallBalanceResult::Already) { + confirmed(); + } + }; + Settings::MaybeRequestBalanceIncrease( + show, + required, + Settings::SmallBalanceForMessage{ .recipientId = peer->id }, + done); + }; + + const auto session = &peer->session(); + if (session->local().isPeerTrustedPayForMessage(peer->id)) { + check(); + return; + } + const auto messages = details.messages; + const auto stars = details.stars; + show->showBox(Box([=](not_null box) { + const auto trust = std::make_shared>(); + const auto proceed = [=](Fn close) { + if ((*trust)->checked()) { + session->local().markPeerTrustedPayForMessage(peer->id); + } + check(); + close(); + }; + Ui::ConfirmBox(box, { + .text = tr::lng_payment_confirm_text( + tr::now, + lt_count, + stars / messages, + lt_name, + Ui::Text::Bold(peer->shortName()), + Ui::Text::RichLangValue).append(' ').append( + tr::lng_payment_confirm_sure( + tr::now, + lt_count, + messages, + lt_amount, + tr::lng_payment_confirm_amount( + tr::now, + lt_count, + stars, + Ui::Text::RichLangValue), + Ui::Text::RichLangValue)), + .confirmed = proceed, + .confirmText = tr::lng_payment_confirm_button( + lt_count, + rpl::single(messages * 1.)), + .title = tr::lng_payment_confirm_title(), + }); + const auto skip = st::defaultCheckbox.margin.top(); + *trust = box->addRow( + object_ptr( + box, + tr::lng_payment_confirm_dont_ask(tr::now)), + st::boxRowPadding + QMargins(0, skip, 0, skip)); + })); +} + void RequestDependentMessageItem( not_null item, PeerId peerId, diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index e462d3bd9..8265c21b9 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -16,6 +16,10 @@ struct SendOptions; struct SendAction; } // namespace Api +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + namespace Data { class Story; class Thread; @@ -31,6 +35,10 @@ namespace Ui { class BoxContent; } // namespace Ui +namespace Window { +class SessionNavigation; +} // namespace Window + struct PreparedServiceText { TextWithEntities text; std::vector links; @@ -135,6 +143,17 @@ struct SendPaymentDetails { not_null peer, int messagesCount); +void ShowSendPaidConfirm( + not_null navigation, + not_null peer, + SendPaymentDetails details, + Fn confirmed); +void ShowSendPaidConfirm( + std::shared_ptr show, + not_null peer, + SendPaymentDetails details, + Fn confirmed); + [[nodiscard]] Data::SendErrorWithThread GetErrorForSending( const std::vector> &threads, SendingErrorRequest request); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index f2095aac4..bf6c33cc5 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -225,6 +225,14 @@ const auto kPsaAboutPrefix = "cloud_lng_about_psa_"; } // namespace +struct HistoryWidget::SendingFiles { + Ui::PreparedList list; + Ui::SendFilesWay way; + TextWithTags caption; + Api::SendOptions options; + bool ctrlShiftEnter = false; +}; + HistoryWidget::HistoryWidget( QWidget *parent, not_null controller) @@ -5896,8 +5904,7 @@ bool HistoryWidget::showSendMessageError( const TextWithTags &textWithTags, bool ignoreSlowmodeCountdown, Fn resend, - int starsApproved, - bool mediaMessage) { + int starsApproved) { if (!_canSendMessages) { return false; } @@ -5908,8 +5915,7 @@ bool HistoryWidget::showSendMessageError( .text = &textWithTags, .ignoreSlowmodeCountdown = ignoreSlowmodeCountdown, }; - request.messagesCount = ComputeSendingMessagesCount(_history, request) - + (mediaMessage ? 1 : 0); + request.messagesCount = ComputeSendingMessagesCount(_history, request); const auto error = GetErrorForSending(_peer, request); if (error) { Data::ShowSendErrorToast(controller(), _peer, error); @@ -5926,14 +5932,14 @@ bool HistoryWidget::checkSendPayment( const auto details = ComputePaymentDetails(_peer, messagesCount); if (!details) { _resendOnFullUpdated = [=] { resend(starsApproved); }; - return true; - } else if (const auto stars = details->stars) { - Data::ShowSendPaidConfirm(controller(), _peer, error, [=] { + return false; + } else if (const auto stars = details->stars; stars > starsApproved) { + ShowSendPaidConfirm(controller(), _peer, *details, [=] { resend(stars); }); - return true; + return false; } - return false; + return true; } bool HistoryWidget::confirmSendingFiles(const QStringList &files) { @@ -6019,28 +6025,53 @@ bool HistoryWidget::confirmSendingFiles( } void HistoryWidget::sendingFilesConfirmed( - Ui::PreparedList &&list, - Ui::SendFilesWay way, - TextWithTags &&caption, - Api::SendOptions options, - bool ctrlShiftEnter) { + Ui::PreparedList &&list, + Ui::SendFilesWay way, + TextWithTags &&caption, + Api::SendOptions options, + bool ctrlShiftEnter) { Expects(list.filesToProcess.empty()); const auto compress = way.sendImagesAsPhotos(); if (showSendingFilesError(list, compress)) { return; } + + sendingFilesConfirmed(std::make_shared(SendingFiles{ + .list = std::move(list), + .way = way, + .caption = std::move(caption), + .options = options, + .ctrlShiftEnter = ctrlShiftEnter, + })); +} + +void HistoryWidget::sendingFilesConfirmed( + std::shared_ptr args) { + const auto withPaymentApproved = [=](int approved) { + args->options.starsApproved = approved; + sendingFilesConfirmed(args); + }; + const auto checked = checkSendPayment( + args->list.files.size(), + args->options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + auto groups = DivideByGroups( - std::move(list), - way, + std::move(args->list), + args->way, _peer->slowmodeApplied()); + const auto compress = args->way.sendImagesAsPhotos(); const auto type = compress ? SendMediaType::Photo : SendMediaType::File; - auto action = prepareSendAction(options); + auto action = prepareSendAction(args->options); action.clearDraft = false; if ((groups.size() != 1 || !groups.front().sentWithCaption()) - && !caption.text.isEmpty()) { + && !args->caption.text.isEmpty()) { auto message = Api::MessageToSend(action); - message.textWithTags = base::take(caption); + message.textWithTags = base::take(args->caption); session().api().sendMessage(std::move(message)); } for (auto &group : groups) { @@ -6050,7 +6081,7 @@ void HistoryWidget::sendingFilesConfirmed( session().api().sendFiles( std::move(group.list), type, - base::take(caption), + base::take(args->caption), album, action); } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 96abc7e1e..425419530 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -320,6 +320,7 @@ private: using TabbedPanel = ChatHelpers::TabbedPanel; using TabbedSelector = ChatHelpers::TabbedSelector; using VoiceToSend = HistoryView::Controls::VoiceToSend; + struct SendingFiles; enum ScrollChangeType { ScrollChangeNone, @@ -472,8 +473,7 @@ private: const TextWithTags &textWithTags, bool ignoreSlowmodeCountdown, Fn resend = nullptr, - int starsApproved = 0, - bool mediaMessage = false); + int starsApproved = 0); bool checkSendPayment( int messagesCount, int starsApproved, @@ -485,6 +485,7 @@ private: TextWithTags &&caption, Api::SendOptions options, bool ctrlShiftEnter); + void sendingFilesConfirmed(std::shared_ptr args); void uploadFile(const QByteArray &fileContent, SendMediaType type); void itemRemoved(not_null item); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 1dc5c6cae..9e28b6188 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -431,35 +431,7 @@ Message::Message( _rightAction->second->link = ReportSponsoredClickHandler(data); } } - if (const auto stars = data->starsPaid()) { - auto text = PreparedServiceText{ - .text = data->out() - ? tr::lng_action_paid_message_sent( - tr::now, - lt_count, - stars, - Ui::Text::WithEntities) - : history()->peer->isUser() - ? tr::lng_action_paid_message_got( - tr::now, - lt_count, - stars, - lt_name, - Ui::Text::Link(data->from()->shortName(), 1), - Ui::Text::WithEntities) - : tr::lng_action_paid_message_group( - tr::now, - lt_count, - stars, - lt_from, - Ui::Text::Link(data->from()->shortName(), 1), - Ui::Text::WithEntities), - }; - if (!data->out()) { - text.links.push_back(data->from()->createOpenLink()); - } - setServicePreMessage(std::move(text)); - } + initPaidInformation(); } Message::~Message() { @@ -470,6 +442,61 @@ Message::~Message() { } } +void Message::initPaidInformation() { + const auto item = data(); + const auto media = this->media(); + const auto mine = PaidInformation{ + .messages = 1, + .stars = item->starsPaid(), + }; + auto info = media ? media->paidInformation().value_or(mine) : mine; + if (!info) { + return; + } + const auto action = [&] { + return (info.messages == 1) + ? tr::lng_action_paid_message_one( + tr::now, + Ui::Text::WithEntities) + : tr::lng_action_paid_message_some( + tr::now, + lt_count, + info.messages, + Ui::Text::WithEntities); + }; + auto text = PreparedServiceText{ + .text = item->out() + ? tr::lng_action_paid_message_sent( + tr::now, + lt_count, + info.stars, + lt_action, + action(), + Ui::Text::WithEntities) + : history()->peer->isUser() + ? tr::lng_action_paid_message_got( + tr::now, + lt_count, + info.stars, + lt_name, + Ui::Text::Link(item->from()->shortName(), 1), + Ui::Text::WithEntities) + : tr::lng_action_paid_message_group( + tr::now, + lt_count, + info.stars, + lt_from, + Ui::Text::Link(item->from()->shortName(), 1), + lt_action, + action(), + Ui::Text::WithEntities), + }; + if (!item->out()) { + text.links.push_back(item ->from()->createOpenLink()); + } + setServicePreMessage(std::move(text)); +} + void Message::refreshRightBadge() { const auto item = data(); const auto text = [&] { diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 735126d30..b33b29edc 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -178,6 +178,7 @@ private: bool updateBottomInfo(); + void initPaidInformation(); void initLogEntryOriginal(); void initPsa(); void fromNameUpdated(int width) const; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index 93c877b66..844f0465c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -78,6 +78,15 @@ enum class MediaInBubbleState : uchar { TimeId duration, const QString &base); +struct PaidInformation { + int messages = 0; + int stars = 0; + + explicit operator bool() const { + return stars != 0; + } +}; + class Media : public Object, public base::has_weak_ptr { public: explicit Media(not_null parent) : _parent(parent) { @@ -121,6 +130,10 @@ public: [[nodiscard]] virtual bool allowsFastShare() const { return false; } + [[nodiscard]] virtual auto paidInformation() const + -> std::optional { + return {}; + } virtual void refreshParentId(not_null realParent) { } virtual void drawHighlight( diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 2b7d2cf99..60c14205a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -867,6 +867,15 @@ QPoint GroupedMedia::resolveCustomInfoRightBottom() const { return QPoint(width() - skipx, height() - skipy); } +std::optional GroupedMedia::paidInformation() const { + auto result = PaidInformation(); + for (const auto &part : _parts) { + ++result.messages; + result.stars += part.item->starsPaid(); + } + return result; +} + bool GroupedMedia::enforceBubbleWidth() const { return _mode == Mode::Grid; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h index 459eb4b1c..24499a776 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h @@ -93,6 +93,7 @@ public: bool allowsFastShare() const override { return true; } + std::optional paidInformation() const override; bool customHighlight() const override { return true; } diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 69a98e922..5884d4d86 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -236,7 +236,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason; inputReportReasonIllegalDrugs#a8eb2be = ReportReason; inputReportReasonPersonalDetails#9ec7863d = ReportReason; -userFull#8555f3c2 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# sponsored_enabled:flags2.7?true can_view_revenue:flags2.9?true bot_can_manage_emoji_status:flags2.10?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage business_intro:flags2.4?BusinessIntro birthday:flags2.5?Birthday personal_channel_id:flags2.6?long personal_channel_message:flags2.6?int stargifts_count:flags2.8?int starref_program:flags2.11?StarRefProgram bot_verification:flags2.12?BotVerification send_paid_messages_stars:flags2.14?long = UserFull; +userFull#d2234ea0 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# sponsored_enabled:flags2.7?true can_view_revenue:flags2.9?true bot_can_manage_emoji_status:flags2.10?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage business_intro:flags2.4?BusinessIntro birthday:flags2.5?Birthday personal_channel_id:flags2.6?long personal_channel_message:flags2.6?int stargifts_count:flags2.8?int starref_program:flags2.11?StarRefProgram bot_verification:flags2.12?BotVerification send_paid_messages_stars:flags2.14?long = UserFull; contact#145ade0b user_id:long mutual:Bool = Contact; @@ -1496,8 +1496,6 @@ inputStorePaymentStarsTopup#dddd0f56 stars:long currency:string amount:long = In inputStorePaymentStarsGift#1d741ef7 user_id:InputUser stars:long currency:string amount:long = InputStorePaymentPurpose; inputStorePaymentStarsGiveaway#751f08fa flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.3?true stars:long boost_peer:InputPeer additional_peers:flags.1?Vector countries_iso2:flags.2?Vector prize_description:flags.4?string random_id:long until_date:int currency:string amount:long users:int = InputStorePaymentPurpose; -premiumGiftOption#79c059f7 flags:# months:int currency:string amount:long bot_url:flags.1?string store_product:flags.0?string = PremiumGiftOption; - paymentFormMethod#88f8f21b url:string title:string = PaymentFormMethod; emojiStatusEmpty#2de11aae = EmojiStatus; From 101d626d4f2cd651170cbd95269662d5107e29d9 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Feb 2025 14:55:34 +0400 Subject: [PATCH 053/103] Support paid files-with-comment and polls. --- Telegram/SourceFiles/apiwrap.cpp | 5 ++ .../history/history_item_helpers.cpp | 57 ++++++++++++++++++- .../history/history_item_helpers.h | 24 +++++++- .../SourceFiles/history/history_widget.cpp | 44 ++++++++------ .../SourceFiles/window/window_peer_menu.cpp | 33 ++++++++--- 5 files changed, 136 insertions(+), 27 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index dad5a8730..1d6cb2ede 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -577,6 +577,11 @@ void ApiWrap::sendMessageFail( if (show) { show->showToast(tr::lng_error_schedule_limit(tr::now)); } + } else if (error.startsWith(u"ALLOW_PAYMENT_REQUIRED_"_q)) { + if (show) { + show->showToast( + u"Payment requirements changed. Please, try again."_q); + } } if (const auto item = _session->data().message(itemId)) { Assert(randomId != 0); diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 2420807c0..5fb0d9f76 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -258,7 +258,7 @@ void ShowSendPaidConfirm( } void ShowSendPaidConfirm( - std::shared_ptr show, + std::shared_ptr show, not_null peer, SendPaymentDetails details, Fn confirmed) { @@ -330,6 +330,61 @@ void ShowSendPaidConfirm( })); } +bool SendPaymentHelper::check( + not_null navigation, + not_null peer, + int messagesCount, + int starsApproved, + Fn resend) { + return check( + navigation->uiShow(), + peer, + messagesCount, + starsApproved, + std::move(resend)); +} + +bool SendPaymentHelper::check( + std::shared_ptr show, + not_null peer, + int messagesCount, + int starsApproved, + Fn resend) { + _lifetime.destroy(); + const auto details = ComputePaymentDetails(peer, messagesCount); + if (!details) { + _resend = [=] { resend(starsApproved); }; + + if (!peer->session().credits().loaded()) { + peer->session().credits().loadedValue( + ) | rpl::filter( + rpl::mappers::_1 + ) | rpl::take(1) | rpl::start_with_next([=] { + if (const auto callback = base::take(_resend)) { + callback(); + } + }, _lifetime); + } + + peer->session().changes().peerUpdates( + peer, + Data::PeerUpdate::Flag::FullInfo + ) | rpl::start_with_next([=] { + if (const auto callback = base::take(_resend)) { + callback(); + } + }, _lifetime); + + return false; + } else if (const auto stars = details->stars; stars > starsApproved) { + ShowSendPaidConfirm(show, peer, *details, [=] { + resend(stars); + }); + return false; + } + return true; +} + void RequestDependentMessageItem( not_null item, PeerId peerId, diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index 8265c21b9..34d8f5d18 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -29,6 +29,7 @@ struct SendErrorWithThread; namespace Main { class Session; +class SessionShow; } // namespace Main namespace Ui { @@ -149,11 +150,32 @@ void ShowSendPaidConfirm( SendPaymentDetails details, Fn confirmed); void ShowSendPaidConfirm( - std::shared_ptr show, + std::shared_ptr show, not_null peer, SendPaymentDetails details, Fn confirmed); +class SendPaymentHelper final { +public: + [[nodiscard]] bool check( + not_null navigation, + not_null peer, + int messagesCount, + int starsApproved, + Fn resend); + [[nodiscard]] bool check( + std::shared_ptr show, + not_null peer, + int messagesCount, + int starsApproved, + Fn resend); + +private: + Fn _resend; + rpl::lifetime _lifetime; + +}; + [[nodiscard]] Data::SendErrorWithThread GetErrorForSending( const std::vector> &threads, SendingErrorRequest request); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index bf6c33cc5..5e9aa5ca2 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -226,10 +226,12 @@ const auto kPsaAboutPrefix = "cloud_lng_about_psa_"; } // namespace struct HistoryWidget::SendingFiles { - Ui::PreparedList list; + std::vector groups; Ui::SendFilesWay way; TextWithTags caption; Api::SendOptions options; + int totalCount = 0; + bool sendComment = false; bool ctrlShiftEnter = false; }; @@ -895,14 +897,16 @@ HistoryWidget::HistoryWidget( } }, lifetime()); - session().credits().loadedValue( - ) | rpl::filter( - rpl::mappers::_1 - ) | rpl::take(1) | rpl::start_with_next([=] { - if (const auto callback = base::take(_resendOnFullUpdated)) { - callback(); - } - }, lifetime()); + if (!session().credits().loaded()) { + session().credits().loadedValue( + ) | rpl::filter( + rpl::mappers::_1 + ) | rpl::take(1) | rpl::start_with_next([=] { + if (const auto callback = base::take(_resendOnFullUpdated)) { + callback(); + } + }, lifetime()); + } using Type = Data::DefaultNotify; rpl::merge( @@ -6037,11 +6041,20 @@ void HistoryWidget::sendingFilesConfirmed( return; } + const auto filesCount = int(list.files.size()); + auto groups = DivideByGroups( + std::move(list), + way, + _peer->slowmodeApplied()); + const auto sendComment = !caption.text.isEmpty() + && (groups.size() != 1 || !groups.front().sentWithCaption()); sendingFilesConfirmed(std::make_shared(SendingFiles{ - .list = std::move(list), + .groups = std::move(groups), .way = way, .caption = std::move(caption), .options = options, + .totalCount = filesCount + (sendComment ? 1 : 0), + .sendComment = sendComment, .ctrlShiftEnter = ctrlShiftEnter, })); } @@ -6053,28 +6066,23 @@ void HistoryWidget::sendingFilesConfirmed( sendingFilesConfirmed(args); }; const auto checked = checkSendPayment( - args->list.files.size(), + args->totalCount, args->options.starsApproved, withPaymentApproved); if (!checked) { return; } - auto groups = DivideByGroups( - std::move(args->list), - args->way, - _peer->slowmodeApplied()); const auto compress = args->way.sendImagesAsPhotos(); const auto type = compress ? SendMediaType::Photo : SendMediaType::File; auto action = prepareSendAction(args->options); action.clearDraft = false; - if ((groups.size() != 1 || !groups.front().sentWithCaption()) - && !args->caption.text.isEmpty()) { + if (args->sendComment) { auto message = Api::MessageToSend(action); message.textWithTags = base::take(args->caption); session().api().sendMessage(std::move(message)); } - for (auto &group : groups) { + for (auto &group : args->groups) { const auto album = (group.type != Ui::AlbumType::None) ? std::make_shared() : nullptr; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 655ddc6dd..01f48bfc4 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1737,11 +1737,27 @@ void PeerMenuCreatePoll( disabled, sendType, sendMenuDetails); - const auto weak = Ui::MakeWeak(box.data()); - const auto lock = box->lifetime().make_state(false); - box->submitRequests( - ) | rpl::start_with_next([=](const CreatePollBox::Result &result) { - if (std::exchange(*lock, true)) { + struct State { + QPointer weak; + Fn create; + SendPaymentHelper sendPayment; + bool lock = false; + }; + const auto state = std::make_shared(); + state->weak = box; + state->create = [=](const CreatePollBox::Result &result) { + const auto withPaymentApproved = [=](int stars) { + auto copy = result; + copy.options.starsApproved = stars; + state->create(copy); + }; + const auto checked = state->sendPayment.check( + controller, + peer, + 1, + result.options.starsApproved, + withPaymentApproved); + if (!checked || std::exchange(state->lock, true)) { return; } auto action = Api::SendAction( @@ -1755,13 +1771,16 @@ void PeerMenuCreatePoll( action.clearDraft = false; } const auto api = &peer->session().api(); + const auto weak = state->weak; api->polls().create(result.poll, action, crl::guard(weak, [=] { weak->closeBox(); }), crl::guard(weak, [=] { - *lock = false; + state->lock = false; weak->submitFailed(tr::lng_attach_failed(tr::now)); })); - }, box->lifetime()); + }; + box->submitRequests( + ) | rpl::start_with_next(state->create, box->lifetime()); controller->show(std::move(box), Ui::LayerOption::CloseOther); } From 22b99b6d3e71b11d9e808961f19a2d1fffd2dbed Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Feb 2025 16:27:12 +0400 Subject: [PATCH 054/103] Send paid shared contacts. --- .../SourceFiles/window/window_peer_menu.cpp | 48 ++++++++++++++----- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 01f48bfc4..c94db1a8a 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1682,23 +1682,49 @@ void PeerMenuShareContactBox( auto recipient = peer->isUser() ? title : ('\xAB' + title + '\xBB'); - const auto weak = base::make_weak(thread); + struct State { + base::weak_ptr weak; + Fn share; + SendPaymentHelper sendPayment; + }; + const auto state = std::make_shared(); + state->weak = thread; + state->share = [=](Api::SendOptions options) { + const auto strong = state->weak.get(); + if (!strong) { + return; + } + const auto withPaymentApproved = [=](int stars) { + auto copy = options; + copy.starsApproved = stars; + state->share(copy); + }; + const auto checked = state->sendPayment.check( + navigation, + peer, + 1, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + navigation->showThread( + strong, + ShowAtTheEndMsgId, + Window::SectionShow::Way::ClearStack); + auto action = Api::SendAction(strong, options); + action.clearDraft = false; + strong->session().api().shareContact(user, action); + }; + navigation->parentController()->show( Ui::MakeConfirmBox({ .text = tr::lng_forward_share_contact( tr::now, lt_recipient, recipient), - .confirmed = [weak, user, navigation](Fn &&close) { - if (const auto strong = weak.get()) { - navigation->showThread( - strong, - ShowAtTheEndMsgId, - Window::SectionShow::Way::ClearStack); - auto action = Api::SendAction(strong); - action.clearDraft = false; - strong->session().api().shareContact(user, action); - } + .confirmed = [state](Fn &&close) { + state->share({}); close(); }, .confirmText = tr::lng_forward_send(), From 93a590774e16e609bfd6cf789964abb3a799e2f2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Feb 2025 17:49:50 +0400 Subject: [PATCH 055/103] Send paid stickers/gifs/inline-results. --- .../chat_helpers/gifs_list_widget.cpp | 15 +++---- .../chat_helpers/gifs_list_widget.h | 5 ++- .../SourceFiles/history/history_widget.cpp | 42 +++++++++++++++++-- .../view/history_view_replies_section.cpp | 12 ++++-- .../view/history_view_replies_section.h | 4 +- .../view/history_view_scheduled_section.cpp | 10 +++-- .../view/history_view_scheduled_section.h | 4 +- .../inline_bot_layout_internal.cpp | 42 +++++++++---------- .../inline_bots/inline_bot_layout_internal.h | 21 ++++++---- .../inline_bots/inline_bot_layout_item.cpp | 24 ++++++----- .../inline_bots/inline_bot_layout_item.h | 8 ++-- .../inline_bots/inline_bot_result.cpp | 4 +- .../inline_bots/inline_bot_result.h | 4 +- .../inline_bots/inline_results_inner.cpp | 20 +++++---- .../inline_bots/inline_results_inner.h | 4 +- .../media/stories/media_stories_reply.cpp | 10 +++-- .../media/stories/media_stories_reply.h | 4 +- .../business/settings_shortcut_messages.cpp | 16 ++++--- .../SourceFiles/window/window_peer_menu.cpp | 7 ++-- 19 files changed, 161 insertions(+), 95 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp index 89c666188..ba0a303b7 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp @@ -666,14 +666,15 @@ GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif( } GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult( - not_null result) { - auto it = _inlineLayouts.find(result); + std::shared_ptr result) { + const auto raw = result.get(); + auto it = _inlineLayouts.find(raw); if (it == _inlineLayouts.cend()) { if (auto layout = LayoutItem::createLayout( this, - result, + std::move(result), _inlineWithThumb)) { - it = _inlineLayouts.emplace(result, std::move(layout)).first; + it = _inlineLayouts.emplace(raw, std::move(layout)).first; it->second->initDimensions(); } else { return nullptr; @@ -746,8 +747,8 @@ int GifsListWidget::refreshInlineRows(const InlineCacheEntry *entry, bool result from, count ) | ranges::views::transform([&]( - const std::unique_ptr &r) { - return layoutPrepareInlineResult(r.get()); + const std::shared_ptr &r) { + return layoutPrepareInlineResult(r); }) | ranges::views::filter([](const LayoutItem *item) { return item != nullptr; }) | ranges::to>>; @@ -770,7 +771,7 @@ int GifsListWidget::validateExistingInlineRows(const InlineResults &results) { const auto until = _mosaic.validateExistingRows([&]( not_null item, int untilIndex) { - return item->getResult() != results[untilIndex].get(); + return item->getResult().get() != results[untilIndex].get(); }, results.size()); if (_mosaic.empty()) { diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h index 249ba5d0c..355686a34 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h @@ -131,7 +131,7 @@ private: }; using InlineResult = InlineBots::Result; - using InlineResults = std::vector>; + using InlineResults = std::vector>; using LayoutItem = InlineBots::Layout::ItemBase; struct InlineCacheEntry { @@ -162,7 +162,8 @@ private: void clearInlineRows(bool resultsDeleted); LayoutItem *layoutPrepareSavedGif(not_null document); - LayoutItem *layoutPrepareInlineResult(not_null result); + LayoutItem *layoutPrepareInlineResult( + std::shared_ptr result); void deleteUnusedGifLayouts(); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 5e9aa5ca2..9609054cc 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -7329,10 +7329,21 @@ void HistoryWidget::sendInlineResult(InlineBots::ResultSelected result) { return; } else if (showSlowmodeError()) { return; + } else if (const auto error = result.result->getErrorOnSend(_history)) { + Data::ShowSendErrorToast(controller(), _peer, error); + return; } - if (const auto error = result.result->getErrorOnSend(_history)) { - Data::ShowSendErrorToast(controller(), _peer, error); + const auto withPaymentApproved = [=](int approved) { + auto copy = result; + copy.options.starsApproved = approved; + sendInlineResult(copy); + }; + const auto checked = checkSendPayment( + 1, + result.options.starsApproved, + withPaymentApproved); + if (!checked) { return; } @@ -7343,7 +7354,7 @@ void HistoryWidget::sendInlineResult(InlineBots::ResultSelected result) { action.generateLocal = true; session().api().sendInlineResult( result.bot, - result.result, + result.result.get(), action, result.messageSendingFrom.localId); @@ -7984,6 +7995,18 @@ bool HistoryWidget::sendExistingDocument( || ShowSendPremiumError(controller(), document)) { return false; } + const auto withPaymentApproved = [=](int approved) { + auto copy = messageToSend; + copy.action.options.starsApproved = approved; + sendExistingDocument(document, std::move(copy), localId); + }; + const auto checked = checkSendPayment( + 1, + messageToSend.action.options.starsApproved, + withPaymentApproved); + if (!checked) { + return false; + } Api::SendExistingDocument( std::move(messageToSend), @@ -8021,6 +8044,19 @@ bool HistoryWidget::sendExistingPhoto( return false; } + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + sendExistingPhoto(photo, copy); + }; + const auto checked = checkSendPayment( + 1, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return false; + } + Api::SendExistingPhoto( Api::MessageToSend(prepareSendAction(options)), photo); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index a899e94b2..3d64635cd 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -1458,13 +1458,13 @@ bool RepliesWidget::sendExistingPhoto( } void RepliesWidget::sendInlineResult( - not_null result, + std::shared_ptr result, not_null bot) { if (const auto error = result->getErrorOnSend(_history)) { Data::ShowSendErrorToast(controller(), _history->peer, error); return; } - sendInlineResult(result, bot, {}, std::nullopt); + sendInlineResult(std::move(result), bot, {}, std::nullopt); //const auto callback = [=](Api::SendOptions options) { // sendInlineResult(result, bot, options); //}; @@ -1474,13 +1474,17 @@ void RepliesWidget::sendInlineResult( } void RepliesWidget::sendInlineResult( - not_null result, + std::shared_ptr result, not_null bot, Api::SendOptions options, std::optional localMessageId) { auto action = prepareSendAction(options); action.generateLocal = true; - session().api().sendInlineResult(bot, result, action, localMessageId); + session().api().sendInlineResult( + bot, + result.get(), + action, + localMessageId); _composeControls->clear(); //_saveDraftText = true; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 8af0e66dd..7f3a6f4dc 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -318,10 +318,10 @@ private: not_null photo, Api::SendOptions options); void sendInlineResult( - not_null result, + std::shared_ptr result, not_null bot); void sendInlineResult( - not_null result, + std::shared_ptr result, not_null bot, Api::SendOptions options, std::optional localMessageId); diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 950ae9f17..6313d88d0 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -909,7 +909,7 @@ bool ScheduledWidget::sendExistingPhoto( } void ScheduledWidget::sendInlineResult( - not_null result, + std::shared_ptr result, not_null bot) { if (const auto error = result->getErrorOnSend(_history)) { Data::ShowSendErrorToast(controller(), _history->peer, error); @@ -923,12 +923,16 @@ void ScheduledWidget::sendInlineResult( } void ScheduledWidget::sendInlineResult( - not_null result, + std::shared_ptr result, not_null bot, Api::SendOptions options) { auto action = prepareSendAction(options); action.generateLocal = true; - session().api().sendInlineResult(bot, result, action, std::nullopt); + session().api().sendInlineResult( + bot, + result.get(), + action, + std::nullopt); _composeControls->clear(); //_saveDraftText = true; diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h index a2e3a5438..bd79ded77 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -274,10 +274,10 @@ private: not_null photo, Api::SendOptions options); void sendInlineResult( - not_null result, + std::shared_ptr result, not_null bot); void sendInlineResult( - not_null result, + std::shared_ptr result, not_null bot, Api::SendOptions options); diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 616280a7e..7f6f41581 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -49,8 +49,8 @@ constexpr auto kMaxInlineArea = 1280 * 720; return dimensions.width() * dimensions.height() <= kMaxInlineArea; } -FileBase::FileBase(not_null context, not_null result) -: ItemBase(context, result) { +FileBase::FileBase(not_null context, std::shared_ptr result) +: ItemBase(context, std::move(result)) { } FileBase::FileBase( @@ -95,8 +95,8 @@ int FileBase::content_duration() const { return getResultDuration(); } -Gif::Gif(not_null context, not_null result) -: FileBase(context, result) { +Gif::Gif(not_null context, std::shared_ptr result) +: FileBase(context, std::move(result)) { Expects(getResultDocument() != nullptr); } @@ -442,8 +442,8 @@ void Gif::clipCallback(Media::Clip::Notification notification) { } } -Sticker::Sticker(not_null context, not_null result) -: FileBase(context, result) { +Sticker::Sticker(not_null context, std::shared_ptr result) +: FileBase(context, std::move(result)) { Expects(getResultDocument() != nullptr); } @@ -654,8 +654,8 @@ void Sticker::clipCallback(Media::Clip::Notification notification) { update(); } -Photo::Photo(not_null context, not_null result) -: ItemBase(context, result) { +Photo::Photo(not_null context, std::shared_ptr result) +: ItemBase(context, std::move(result)) { Expects(getShownPhoto() != nullptr); } @@ -769,8 +769,8 @@ void Photo::prepareThumbnail(QSize size, QSize frame) const { validateThumbnail(_photoMedia->thumbnailInline(), size, frame, false); } -Video::Video(not_null context, not_null result) -: FileBase(context, result) +Video::Video(not_null context, std::shared_ptr result) +: FileBase(context, std::move(result)) , _link(getResultPreviewHandler()) , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { @@ -925,11 +925,11 @@ void CancelFileClickHandler::onClickImpl() const { _result->cancelFile(); } -File::File(not_null context, not_null result) -: FileBase(context, result) +File::File(not_null context, std::shared_ptr result) +: FileBase(context, std::move(result)) , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineFileSize - st::inlineThumbSkip) , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineFileSize - st::inlineThumbSkip) -, _cancel(std::make_shared(result)) +, _cancel(std::make_shared(_result.get())) , _document(getShownDocument()) { Expects(getResultDocument() != nullptr); @@ -1173,8 +1173,8 @@ void File::setStatusSize( } } -Contact::Contact(not_null context, not_null result) -: ItemBase(context, result) +Contact::Contact(not_null context, std::shared_ptr result) +: ItemBase(context, std::move(result)) , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { } @@ -1265,16 +1265,16 @@ void Contact::prepareThumbnail(int width, int height) const { Article::Article( not_null context, - not_null result, + std::shared_ptr result, bool withThumb) -: ItemBase(context, result) +: ItemBase(context, std::move(result)) , _url(getResultUrlHandler()) , _link(getResultPreviewHandler()) , _withThumb(withThumb) , _title(st::emojiPanWidth / 2) , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { if (!_link) { - if (const auto point = result->getLocationPoint()) { + if (const auto point = _result->getLocationPoint()) { _link = std::make_shared(*point); } } @@ -1300,7 +1300,7 @@ void Article::initDimensions() { _minh += st::inlineRowMargin * 2 + st::inlineRowBorder; } -int32 Article::resizeGetHeight(int32 width) { +int Article::resizeGetHeight(int width) { _width = qMin(width, _maxw); if (_url) { _urlText = getResultUrl(); @@ -1422,8 +1422,8 @@ void Article::prepareThumbnail(int width, int height) const { }); } -Game::Game(not_null context, not_null result) -: ItemBase(context, result) +Game::Game(not_null context, std::shared_ptr result) +: ItemBase(context, std::move(result)) , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { countFrameSize(); diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h index 83794b1b6..e483f858a 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h @@ -29,7 +29,7 @@ namespace internal { class FileBase : public ItemBase { public: - FileBase(not_null context, not_null result); + FileBase(not_null context, std::shared_ptr result); // For saved gif layouts. FileBase(not_null context, not_null document); @@ -58,7 +58,7 @@ private: class Gif final : public FileBase { public: - Gif(not_null context, not_null result); + Gif(not_null context, std::shared_ptr result); Gif( not_null context, not_null document, @@ -138,7 +138,7 @@ private: class Photo : public ItemBase { public: - Photo(not_null context, not_null result); + Photo(not_null context, std::shared_ptr result); // Not used anywhere currently. //Photo(not_null context, not_null photo); @@ -178,7 +178,7 @@ private: class Sticker : public FileBase { public: - Sticker(not_null context, not_null result); + Sticker(not_null context, std::shared_ptr result); ~Sticker(); // Not used anywhere currently. //Sticker(not_null context, not_null document); @@ -229,7 +229,7 @@ private: class Video : public FileBase { public: - Video(not_null context, not_null result); + Video(not_null context, std::shared_ptr result); void initDimensions() override; @@ -269,7 +269,7 @@ private: class File : public FileBase { public: - File(not_null context, not_null result); + File(not_null context, std::shared_ptr result); ~File(); void initDimensions() override; @@ -347,7 +347,7 @@ private: class Contact : public ItemBase { public: - Contact(not_null context, not_null result); + Contact(not_null context, std::shared_ptr result); void initDimensions() override; @@ -366,7 +366,10 @@ private: class Article : public ItemBase { public: - Article(not_null context, not_null result, bool withThumb); + Article( + not_null context, + std::shared_ptr result, + bool withThumb); void initDimensions() override; int resizeGetHeight(int width) override; @@ -391,7 +394,7 @@ private: class Game : public ItemBase { public: - Game(not_null context, not_null result); + Game(not_null context, std::shared_ptr result); void setPosition(int32 position) override; void initDimensions() override; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp index 34acc4093..89b0e41b1 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp @@ -29,7 +29,7 @@ base::NeverFreedPointer documentItemsMap; } // namespace -Result *ItemBase::getResult() const { +std::shared_ptr ItemBase::getResult() const { return _result; } @@ -92,33 +92,37 @@ void ItemBase::layoutChanged() { std::unique_ptr ItemBase::createLayout( not_null context, - not_null result, + std::shared_ptr result, bool forceThumb) { using Type = Result::Type; switch (result->_type) { case Type::Photo: - return std::make_unique(context, result); + return std::make_unique(context, std::move(result)); case Type::Audio: case Type::File: - return std::make_unique(context, result); + return std::make_unique(context, std::move(result)); case Type::Video: - return std::make_unique(context, result); + return std::make_unique(context, std::move(result)); case Type::Sticker: - return std::make_unique(context, result); + return std::make_unique( + context, + std::move(result)); case Type::Gif: - return std::make_unique(context, result); + return std::make_unique(context, std::move(result)); case Type::Article: case Type::Geo: case Type::Venue: return std::make_unique( context, - result, + std::move(result), forceThumb); case Type::Game: - return std::make_unique(context, result); + return std::make_unique(context, std::move(result)); case Type::Contact: - return std::make_unique(context, result); + return std::make_unique( + context, + std::move(result)); } return nullptr; } diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h index 67574f7e0..e370b3477 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h @@ -59,7 +59,7 @@ public: class ItemBase : public LayoutItemBase { public: - ItemBase(not_null context, not_null result) + ItemBase(not_null context, std::shared_ptr result) : _result(result) , _context(context) { } @@ -80,7 +80,7 @@ public: return false; } - Result *getResult() const; + std::shared_ptr getResult() const; DocumentData *getDocument() const; PhotoData *getPhoto() const; @@ -112,7 +112,7 @@ public: static std::unique_ptr createLayout( not_null context, - not_null result, + std::shared_ptr result, bool forceThumb); static std::unique_ptr createLayoutGif( not_null context, @@ -135,7 +135,7 @@ protected: } Data::FileOrigin fileOrigin() const; - Result *_result = nullptr; + std::shared_ptr _result; DocumentData *_document = nullptr; PhotoData *_photo = nullptr; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp index e5e1566d5..0b5c01cd3 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp @@ -53,7 +53,7 @@ Result::Result(not_null session, const Creator &creator) , _type(creator.type) { } -std::unique_ptr Result::Create( +std::shared_ptr Result::Create( not_null session, uint64 queryId, const MTPBotInlineResult &data) { @@ -84,7 +84,7 @@ std::unique_ptr Result::Create( return nullptr; } - auto result = std::make_unique( + auto result = std::make_shared( session, Creator{ queryId, type }); const auto message = data.match([&](const MTPDbotInlineResult &data) { diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.h b/Telegram/SourceFiles/inline_bots/inline_bot_result.h index 67b3a0fd1..bf40bc2ee 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_result.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.h @@ -43,7 +43,7 @@ public: // You should use create() static method instead. Result(not_null session, const Creator &creator); - static std::unique_ptr Create( + static std::shared_ptr Create( not_null session, uint64 queryId, const MTPBotInlineResult &mtpData); @@ -130,7 +130,7 @@ private: }; struct ResultSelected { - not_null result; + std::shared_ptr result; not_null bot; PeerData *recipientOverride = nullptr; Api::SendOptions options; diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp index c2ab83e95..41ee7f426 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp @@ -331,7 +331,7 @@ void Inner::selectInlineResult( if (const auto inlineResult = item->getResult()) { if (inlineResult->onChoose(item)) { _resultSelectedCallback({ - .result = inlineResult, + .result = std::move(inlineResult), .bot = _inlineBot, .options = std::move(options), .messageSendingFrom = messageSendingFrom(), @@ -448,11 +448,15 @@ void Inner::clearInlineRows(bool resultsDeleted) { _mosaic.clearRows(resultsDeleted); } -ItemBase *Inner::layoutPrepareInlineResult(Result *result) { - auto it = _inlineLayouts.find(result); +ItemBase *Inner::layoutPrepareInlineResult(std::shared_ptr result) { + const auto raw = result.get(); + auto it = _inlineLayouts.find(raw); if (it == _inlineLayouts.cend()) { - if (auto layout = ItemBase::createLayout(this, result, _inlineWithThumb)) { - it = _inlineLayouts.emplace(result, std::move(layout)).first; + if (auto layout = ItemBase::createLayout( + this, + std::move(result), + _inlineWithThumb)) { + it = _inlineLayouts.emplace(raw, std::move(layout)).first; it->second->initDimensions(); } else { return nullptr; @@ -560,8 +564,8 @@ int Inner::refreshInlineRows(PeerData *queryPeer, UserData *bot, const CacheEntr const auto resultItems = entry->results | ranges::views::slice( from, count - ) | ranges::views::transform([&](const std::unique_ptr &r) { - return layoutPrepareInlineResult(r.get()); + ) | ranges::views::transform([&](const std::shared_ptr &r) { + return layoutPrepareInlineResult(r); }) | ranges::views::filter([](const ItemBase *item) { return item != nullptr; }) | ranges::to>>; @@ -585,7 +589,7 @@ int Inner::validateExistingInlineRows(const Results &results) { const auto until = _mosaic.validateExistingRows([&]( not_null item, int untilIndex) { - return item->getResult() != results[untilIndex].get(); + return item->getResult().get() != results[untilIndex].get(); }, results.size()); if (_mosaic.empty()) { diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.h b/Telegram/SourceFiles/inline_bots/inline_results_inner.h index 28b305513..e7d47a30a 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_inner.h +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.h @@ -50,7 +50,7 @@ namespace InlineBots { namespace Layout { class ItemBase; -using Results = std::vector>; +using Results = std::vector>; struct CacheEntry { QString nextOffset; @@ -135,7 +135,7 @@ private: void updateInlineItems(); void repaintItems(crl::time now = 0); void clearInlineRows(bool resultsDeleted); - ItemBase *layoutPrepareInlineResult(Result *result); + ItemBase *layoutPrepareInlineResult(std::shared_ptr result); void updateRestrictedLabelGeometry(); void deleteUnusedInlineLayouts(); diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index 934d86243..ad06525c3 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -307,7 +307,7 @@ bool ReplyArea::sendExistingPhoto( } void ReplyArea::sendInlineResult( - not_null result, + std::shared_ptr result, not_null bot) { if (const auto error = result->getErrorOnSend(history())) { const auto show = _controller->uiShow(); @@ -318,13 +318,17 @@ void ReplyArea::sendInlineResult( } void ReplyArea::sendInlineResult( - not_null result, + std::shared_ptr result, not_null bot, Api::SendOptions options, std::optional localMessageId) { auto action = prepareSendAction(options); action.generateLocal = true; - session().api().sendInlineResult(bot, result, action, localMessageId); + session().api().sendInlineResult( + bot, + result.get(), + action, + localMessageId); auto &bots = cRefRecentInlineBots(); const auto index = bots.indexOf(bot); diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.h b/Telegram/SourceFiles/media/stories/media_stories_reply.h index 981cc48c1..d3cdec47d 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.h @@ -127,10 +127,10 @@ private: not_null photo, Api::SendOptions options); void sendInlineResult( - not_null result, + std::shared_ptr result, not_null bot); void sendInlineResult( - not_null result, + std::shared_ptr result, not_null bot, Api::SendOptions options, std::optional localMessageId); diff --git a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp index 995a98202..db5a4d99a 100644 --- a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp +++ b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp @@ -223,10 +223,10 @@ private: not_null photo, Api::SendOptions options); void sendInlineResult( - not_null result, + std::shared_ptr result, not_null bot); void sendInlineResult( - not_null result, + std::shared_ptr result, not_null bot, Api::SendOptions options, std::optional localMessageId); @@ -1534,7 +1534,7 @@ bool ShortcutMessages::sendExistingPhoto( } void ShortcutMessages::sendInlineResult( - not_null result, + std::shared_ptr result, not_null bot) { if (showPremiumRequired()) { return; @@ -1542,11 +1542,11 @@ void ShortcutMessages::sendInlineResult( Data::ShowSendErrorToast(_controller, _history->peer, error); return; } - sendInlineResult(result, bot, {}, std::nullopt); + sendInlineResult(std::move(result), bot, {}, std::nullopt); } void ShortcutMessages::sendInlineResult( - not_null result, + std::shared_ptr result, not_null bot, Api::SendOptions options, std::optional localMessageId) { @@ -1555,7 +1555,11 @@ void ShortcutMessages::sendInlineResult( } auto action = prepareSendAction(options); action.generateLocal = true; - _session->api().sendInlineResult(bot, result, action, localMessageId); + _session->api().sendInlineResult( + bot, + result.get(), + action, + localMessageId); _composeControls->clear(); //_saveDraftText = true; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index c94db1a8a..760c27537 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1684,12 +1684,12 @@ void PeerMenuShareContactBox( : ('\xAB' + title + '\xBB'); struct State { base::weak_ptr weak; - Fn share; + FnMut share; SendPaymentHelper sendPayment; }; - const auto state = std::make_shared(); + auto state = std::make_shared(); state->weak = thread; - state->share = [=](Api::SendOptions options) { + state->share = [=](Api::SendOptions options) mutable { const auto strong = state->weak.get(); if (!strong) { return; @@ -1715,6 +1715,7 @@ void PeerMenuShareContactBox( auto action = Api::SendAction(strong, options); action.clearDraft = false; strong->session().api().shareContact(user, action); + state = nullptr; }; navigation->parentController()->show( From 37dd648686ef6774845165a60924509f155181d2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Feb 2025 18:12:35 +0400 Subject: [PATCH 056/103] Implement paid location sending. --- .../inline_bots/bot_attach_web_view.cpp | 31 +++++++++++++++- .../SourceFiles/window/window_peer_menu.cpp | 36 ++++++++++--------- 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 2e7ef597f..09a643580 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/stickers/data_stickers.h" #include "history/history.h" #include "history/history_item.h" +#include "history/history_item_helpers.h" #include "info/bot/starref/info_bot_starref_common.h" // MakePeerBubbleButton #include "info/profile/info_profile_values.h" #include "inline_bots/inline_bot_result.h" @@ -2482,18 +2483,46 @@ void ChooseAndSendLocation( not_null controller, const Ui::LocationPickerConfig &config, Api::SendAction action) { + const auto weak = base::make_weak(controller); const auto session = &controller->session(); if (const auto picker = session->locationPickers().lookup(action)) { picker->activate(); return; } - const auto callback = [=](Data::InputVenue venue) { + struct State { + SendPaymentHelper sendPayment; + Fn send; + }; + const auto state = std::make_shared(); + state->send = [=](Data::InputVenue venue, Api::SendAction action) { + if (const auto strong = weak.get()) { + const auto withPaymentApproved = [=](int stars) { + if (const auto onstack = state->send) { + auto copy = action; + copy.options.starsApproved = stars; + onstack(venue, copy); + } + }; + const auto checked = state->sendPayment.check( + strong, + action.history->peer, + 1, + action.options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + } + state->send = nullptr; if (venue.justLocation()) { Api::SendLocation(action, venue.lat, venue.lon); } else { Api::SendVenue(action, venue); } }; + const auto callback = [=](Data::InputVenue venue) { + state->send(venue, action); + }; const auto picker = Ui::LocationPicker::Show({ .parent = controller->widget(), .config = config, diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 760c27537..8854d89a0 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1684,20 +1684,23 @@ void PeerMenuShareContactBox( : ('\xAB' + title + '\xBB'); struct State { base::weak_ptr weak; - FnMut share; + Fn share; SendPaymentHelper sendPayment; }; - auto state = std::make_shared(); + const auto state = std::make_shared(); state->weak = thread; - state->share = [=](Api::SendOptions options) mutable { + state->share = [=](Api::SendOptions options) { const auto strong = state->weak.get(); if (!strong) { + state->share = nullptr; return; } const auto withPaymentApproved = [=](int stars) { - auto copy = options; - copy.starsApproved = stars; - state->share(copy); + if (const auto onstack = state->share) { + auto copy = options; + copy.starsApproved = stars; + onstack(copy); + } }; const auto checked = state->sendPayment.check( navigation, @@ -1715,7 +1718,7 @@ void PeerMenuShareContactBox( auto action = Api::SendAction(strong, options); action.clearDraft = false; strong->session().api().shareContact(user, action); - state = nullptr; + state->share = nullptr; }; navigation->parentController()->show( @@ -1765,19 +1768,20 @@ void PeerMenuCreatePoll( sendType, sendMenuDetails); struct State { - QPointer weak; Fn create; SendPaymentHelper sendPayment; bool lock = false; }; - const auto state = std::make_shared(); - state->weak = box; + const auto weak = QPointer(box); + const auto state = box->lifetime().make_state(); state->create = [=](const CreatePollBox::Result &result) { - const auto withPaymentApproved = [=](int stars) { - auto copy = result; - copy.options.starsApproved = stars; - state->create(copy); - }; + const auto withPaymentApproved = crl::guard(weak, [=](int stars) { + if (const auto onstack = state->create) { + auto copy = result; + copy.options.starsApproved = stars; + onstack(copy); + } + }); const auto checked = state->sendPayment.check( controller, peer, @@ -1798,8 +1802,8 @@ void PeerMenuCreatePoll( action.clearDraft = false; } const auto api = &peer->session().api(); - const auto weak = state->weak; api->polls().create(result.poll, action, crl::guard(weak, [=] { + state->create = nullptr; weak->closeBox(); }), crl::guard(weak, [=] { state->lock = false; From 928be4151bb8213c1af047dac4735bd8ed0d4aa2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 21 Feb 2025 10:15:26 +0400 Subject: [PATCH 057/103] Update API scheme, parse premium gifts for stars. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/api/api_premium.cpp | 16 ++-- Telegram/SourceFiles/api/api_premium.h | 3 +- Telegram/SourceFiles/boxes/star_gift_box.cpp | 73 ++++++++++++++----- .../info/peer_gifts/info_peer_gifts_common.h | 1 + Telegram/SourceFiles/mtproto/scheme/api.tl | 2 +- 6 files changed, 70 insertions(+), 26 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 5b378b5d1..aac02c54a 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3341,6 +3341,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_send_anonymous" = "Hide My Name"; "lng_gift_send_anonymous_self" = "Hide my name and message from visitors to my profile."; "lng_gift_send_anonymous_about" = "You can hide your name and message from visitors to {user}'s profile. {recipient} will still see your name and message."; +"lng_gift_send_anonymous_about_paid" = "You can hide your name from visitors to {user}'s profile. {recipient} will still see your name."; "lng_gift_send_anonymous_about_channel" = "You can hide your name and message from all visitors of this channel except its admins."; "lng_gift_send_unique" = "Make Unique for {price}"; "lng_gift_send_unique_about" = "Enable this to let {user} turn your gift into a unique collectible. {link}"; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 26913462b..b44ca20ed 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -482,6 +482,7 @@ rpl::producer PremiumGiftCodeOptions::request() { const auto token = Token{ data.vusers().v, data.vmonths().v }; _stores[token] = Store{ .amount = data.vamount().v, + .currency = qs(data.vcurrency()), .product = qs(data.vstore_product().value_or_empty()), .quantity = data.vstore_quantity().value_or_empty(), }; @@ -490,14 +491,14 @@ rpl::producer PremiumGiftCodeOptions::request() { } } for (const auto &[amount, tlOptions] : tlMapOptions) { - if (amount == 1 && _optionsForOnePerson.currency.isEmpty()) { - _optionsForOnePerson.currency = qs( - tlOptions.front().data().vcurrency()); + if (amount == 1 && _optionsForOnePerson.currencies.empty()) { for (const auto &option : tlOptions) { _optionsForOnePerson.months.push_back( option.data().vmonths().v); _optionsForOnePerson.totalCosts.push_back( option.data().vamount().v); + _optionsForOnePerson.currencies.push_back( + qs(option.data().vcurrency())); } } _subscriptionOptions[amount] = GiftCodesFromTL(tlOptions); @@ -555,7 +556,7 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice( const auto token = Token{ users, months }; const auto &store = _stores[token]; return Payments::InvoicePremiumGiftCode{ - .currency = _optionsForOnePerson.currency, + .currency = store.currency, .storeProduct = store.product, .randomId = randomId, .amount = store.amount, @@ -568,14 +569,15 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice( std::vector PremiumGiftCodeOptions::optionsForPeer() const { auto result = std::vector(); - if (!_optionsForOnePerson.currency.isEmpty()) { + if (!_optionsForOnePerson.currencies.empty()) { const auto count = int(_optionsForOnePerson.months.size()); result.reserve(count); for (auto i = 0; i != count; ++i) { Assert(i < _optionsForOnePerson.totalCosts.size()); + Assert(i < _optionsForOnePerson.currencies.size()); result.push_back({ .cost = _optionsForOnePerson.totalCosts[i], - .currency = _optionsForOnePerson.currency, + .currency = _optionsForOnePerson.currencies[i], .months = _optionsForOnePerson.months[i], }); } @@ -596,7 +598,7 @@ Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) { MTP_int(_optionsForOnePerson.months[i]), MTPstring(), MTPint(), - MTP_string(_optionsForOnePerson.currency), + MTP_string(_optionsForOnePerson.currencies[i]), MTP_long(_optionsForOnePerson.totalCosts[i] * amount))); } _subscriptionOptions[amount] = GiftCodesFromTL(tlOptions); diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 0430c754b..5f4d06d83 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -209,6 +209,7 @@ private: }; struct Store final { uint64 amount = 0; + QString currency; QString product; int quantity = 0; }; @@ -219,7 +220,7 @@ private: struct { std::vector months; std::vector totalCosts; - QString currency; + std::vector currencies; } _optionsForOnePerson; std::vector _availablePresets; diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index c99b64959..fcd18526b 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_selector.h" #include "core/ui_integration.h" +#include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_credits.h" #include "data/data_document.h" @@ -80,6 +81,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/checkbox.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/shadow.h" +#include "ui/wrap/slide_wrap.h" #include "window/themes/window_theme.h" #include "window/section_widget.h" #include "window/window_session_controller.h" @@ -622,14 +624,27 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { list.reserve(options.size()); auto minMonthsGift = GiftTypePremium(); for (const auto &option : options) { - list.push_back({ - .cost = option.cost, - .currency = option.currency, - .months = option.months, - }); - if (!minMonthsGift.months - || option.months < minMonthsGift.months) { - minMonthsGift = list.back(); + if (option.currency != kCreditsCurrency) { + list.push_back({ + .cost = option.cost, + .currency = option.currency, + .months = option.months, + }); + if (!minMonthsGift.months + || option.months < minMonthsGift.months) { + minMonthsGift = list.back(); + } + } + } + for (const auto &option : options) { + if (option.currency == kCreditsCurrency) { + const auto i = ranges::find( + list, + option.months, + &GiftTypePremium::months); + if (i != end(list)) { + i->stars = option.cost; + } } } for (auto &gift : list) { @@ -1424,6 +1439,7 @@ void SendGiftBox( struct State { rpl::variable details; + rpl::variable messageAllowed; std::shared_ptr media; bool submitting = false; }; @@ -1432,6 +1448,13 @@ void SendGiftBox( .descriptor = descriptor, .randomId = base::RandomValue(), }; + peer->updateFull(); + state->messageAllowed = peer->session().changes().peerFlagsValue( + peer, + Data::PeerUpdate::Flag::StarsPerMessage + ) | rpl::map([=] { + return peer->starsPerMessageChecked() == 0; + }); auto cost = state->details.value( ) | rpl::map([session](const GiftDetails &details) { @@ -1462,10 +1485,17 @@ void SendGiftBox( peer, state->details.value())); + const auto messageWrap = container->add( + object_ptr>( + container, + object_ptr(container))); + messageWrap->toggleOn(state->messageAllowed.value()); + messageWrap->finishAnimating(); + const auto messageInner = messageWrap->entity(); const auto limit = StarGiftMessageLimit(session); const auto text = AddPartInput( window, - container, + messageInner, box->getDelegate()->outerContainer(), tr::lng_gift_send_message(), QString(), @@ -1509,7 +1539,6 @@ void SendGiftBox( text, session, { .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow }); - if (stars) { const auto cost = stars->info.starsToUpgrade; if (cost > 0 && !peer->isSelf()) { @@ -1552,7 +1581,7 @@ void SendGiftBox( AddSkip(container); } v::match(descriptor, [&](const GiftTypePremium &) { - AddDividerText(container, tr::lng_gift_send_premium_about( + AddDividerText(messageInner, tr::lng_gift_send_premium_about( lt_user, rpl::single(peer->shortName()))); }, [&](const GiftTypeStars &) { @@ -1560,11 +1589,18 @@ void SendGiftBox( ? tr::lng_gift_send_anonymous_self() : peer->isBroadcast() ? tr::lng_gift_send_anonymous_about_channel() - : tr::lng_gift_send_anonymous_about( - lt_user, - rpl::single(peer->shortName()), - lt_recipient, - rpl::single(peer->shortName()))); + : rpl::conditional( + state->messageAllowed.value(), + tr::lng_gift_send_anonymous_about( + lt_user, + rpl::single(peer->shortName()), + lt_recipient, + rpl::single(peer->shortName())), + tr::lng_gift_send_anonymous_about_paid( + lt_user, + rpl::single(peer->shortName()), + lt_recipient, + rpl::single(peer->shortName())))); }); const auto buttonWidth = st::boxWideWidth @@ -1575,7 +1611,10 @@ void SendGiftBox( return; } state->submitting = true; - const auto details = state->details.current(); + auto details = state->details.current(); + if (!state->messageAllowed.current()) { + details.text = {}; + } const auto weak = MakeWeak(box); const auto done = [=](Payments::CheckoutResult result) { if (result == Payments::CheckoutResult::Paid) { diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h index bba226aaa..8362ef85d 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h @@ -44,6 +44,7 @@ namespace Info::PeerGifts { struct GiftTypePremium { int64 cost = 0; QString currency; + int stars = 0; int months = 0; int discountPercent = 0; diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 5884d4d86..84522a174 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -175,7 +175,7 @@ messageActionTopicEdit#c0944820 flags:# title:flags.0?string icon_emoji_id:flags messageActionSuggestProfilePhoto#57de635e photo:Photo = MessageAction; messageActionRequestedPeer#31518e9b button_id:int peers:Vector = MessageAction; messageActionSetChatWallPaper#5060a3f4 flags:# same:flags.0?true for_both:flags.1?true wallpaper:WallPaper = MessageAction; -messageActionGiftCode#56d03994 flags:# via_giveaway:flags.0?true unclaimed:flags.2?true boost_peer:flags.1?Peer months:int slug:string currency:flags.2?string amount:flags.2?long crypto_currency:flags.3?string crypto_amount:flags.3?long message:flags.4?TextWithEntities = MessageAction; +messageActionGiftCode#56d03994 flags:# via_giveaway:flags.0?true unclaimed:flags.5?true boost_peer:flags.1?Peer months:int slug:string currency:flags.2?string amount:flags.2?long crypto_currency:flags.3?string crypto_amount:flags.3?long message:flags.4?TextWithEntities = MessageAction; messageActionGiveawayLaunch#a80f51e4 flags:# stars:flags.0?long = MessageAction; messageActionGiveawayResults#87e2f155 flags:# stars:flags.0?true winners_count:int unclaimed_count:int = MessageAction; messageActionBoostApply#cc02aa6d boosts:int = MessageAction; From 7b7e18e752b79ba6e2065c71094a021b777e0d32 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 21 Feb 2025 21:29:10 +0400 Subject: [PATCH 058/103] Support paid sending in ShareBox. --- Telegram/Resources/langs/lang.strings | 5 + Telegram/SourceFiles/api/api_premium.cpp | 9 +- Telegram/SourceFiles/api/api_premium.h | 8 + .../SourceFiles/boxes/edit_privacy_box.cpp | 2 +- .../boxes/peer_list_controllers.cpp | 144 +++++++++++++----- .../SourceFiles/boxes/peer_list_controllers.h | 42 +++-- .../boxes/peers/edit_peer_invite_link.cpp | 3 + Telegram/SourceFiles/boxes/share_box.cpp | 138 +++++++++++++---- Telegram/SourceFiles/boxes/share_box.h | 3 + .../calls/group/calls_group_settings.cpp | 3 + .../chat_helpers/field_autocomplete.cpp | 3 +- .../history/history_item_helpers.cpp | 113 ++++++++++---- .../history/history_item_helpers.h | 21 ++- .../SourceFiles/history/history_widget.cpp | 20 --- Telegram/SourceFiles/history/history_widget.h | 1 - .../media/stories/media_stories_share.cpp | 5 +- .../payments/ui/payments_reaction_box.cpp | 114 ++++++++------ .../payments/ui/payments_reaction_box.h | 11 ++ .../settings/settings_credits_graphics.cpp | 15 +- .../SourceFiles/window/window_peer_menu.cpp | 1 + 20 files changed, 468 insertions(+), 193 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index aac02c54a..8f6a32ad8 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2795,6 +2795,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_small_balance_subscribe" = "Buy **Stars** and subscribe to **{channel}** and other channels."; "lng_credits_small_balance_star_gift" = "Buy **Stars** to send gifts to {user} and other contacts."; "lng_credits_small_balance_for_message" = "Buy **Stars** to send messages to {user}."; +"lng_credits_small_balance_for_messages" = "Buy **Stars** to send messages."; "lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram."; "lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars."; "lng_credits_enough" = "You have enough stars at the moment. {link}"; @@ -4834,6 +4835,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_payment_confirm_text#other" = "{name} charges **{count}** Stars per message."; "lng_payment_confirm_amount#one" = "**{count}** Star"; "lng_payment_confirm_amount#other" = "**{count}** Stars"; +"lng_payment_confirm_users#one" = "You selected **{count}** user who charge Stars for messages."; +"lng_payment_confirm_users#other" = "You selected **{count}** users who charge Stars for messages."; +"lng_payment_confirm_chats#one" = "You selected **{count}** chat where you pay Stars for messages."; +"lng_payment_confirm_chats#other" = "You selected **{count}** chats where you pay Stars for messages."; "lng_payment_confirm_sure#one" = "Would you like to pay {amount} to send **{count}** message?"; "lng_payment_confirm_sure#other" = "Would you like to pay {amount} to send **{count}** messages?"; "lng_payment_confirm_dont_ask" = "Don't ask me again"; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index b44ca20ed..fe3004cda 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_text_entities.h" #include "apiwrap.h" #include "base/random.h" +#include "data/data_channel.h" #include "data/data_document.h" #include "data/data_peer.h" #include "data/data_peer_values.h" @@ -714,12 +715,18 @@ rpl::producer SponsoredToggle::setToggled(bool v) { MessageMoneyRestriction ResolveMessageMoneyRestrictions( not_null peer, History *maybeHistory) { + if (const auto channel = peer->asChannel()) { + return { + .starsPerMessage = channel->starsPerMessageChecked(), + .known = true, + }; + } const auto user = peer->asUser(); if (!user) { return { .known = true }; } else if (user->messageMoneyRestrictionsKnown()) { return { - .starsPerMessage = user->starsPerMessage(), + .starsPerMessage = user->starsPerMessageChecked(), .premiumRequired = (user->requiresPremiumToWrite() && !user->session().premium()), .known = true, diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 5f4d06d83..2b692a484 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -250,6 +250,14 @@ struct MessageMoneyRestriction { int starsPerMessage = 0; bool premiumRequired = false; bool known = false; + + explicit operator bool() const { + return starsPerMessage != 0 || premiumRequired; + } + + friend inline bool operator==( + const MessageMoneyRestriction &, + const MessageMoneyRestriction &) = default; }; [[nodiscard]] MessageMoneyRestriction ResolveMessageMoneyRestrictions( not_null peer, diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp index f13b5bae0..d905a5d9a 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp @@ -46,7 +46,7 @@ constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value; constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value; constexpr auto kGetPercent = 85; constexpr auto kStarsMin = 1; -constexpr auto kStarsMax = 9000; +constexpr auto kStarsMax = 10000; constexpr auto kDefaultChargeStars = 10; using Exceptions = Api::UserPrivacy::Exceptions; diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index 95105ee60..f4f60bcc9 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peer_list_controllers.h" #include "api/api_chat_participants.h" -#include "api/api_premium.h" +#include "api/api_premium.h" // MessageMoneyRestriction. #include "base/random.h" #include "boxes/filters/edit_filter_chats_list.h" #include "settings/settings_premium.h" @@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "dialogs/dialogs_main_list.h" +#include "payments/ui/payments_reaction_box.h" #include "ui/effects/outline_segments.h" #include "ui/wrap/slide_wrap.h" #include "window/window_separate_id.h" @@ -275,28 +276,56 @@ bool PeerListGlobalSearchController::isLoading() { return _timer.isActive() || _requestId; } +struct RecipientRow::Restriction { + Api::MessageMoneyRestriction value; + RestrictionBadgeCache cache; +}; + RecipientRow::RecipientRow( not_null peer, const style::PeerListItem *maybeLockedSt, History *maybeHistory) : PeerListRow(peer) , _maybeHistory(maybeHistory) -, _resolvePremiumRequired(maybeLockedSt != nullptr) { - if (maybeLockedSt - && Api::ResolveMessageMoneyRestrictions( +, _maybeLockedSt(maybeLockedSt) { + if (_maybeLockedSt) { + setRestriction(Api::ResolveMessageMoneyRestrictions( peer, - maybeHistory).premiumRequired) { - _lockedSt = maybeLockedSt; + maybeHistory)); } } +Api::MessageMoneyRestriction RecipientRow::restriction() const { + return _restriction + ? _restriction->value + : Api::MessageMoneyRestriction(); +} + +void RecipientRow::setRestriction(Api::MessageMoneyRestriction restriction) { + if (!restriction) { + _restriction = nullptr; + return; + } else if (!_restriction) { + _restriction = std::make_unique(); + } + _restriction->value = restriction; +} + PaintRoundImageCallback RecipientRow::generatePaintUserpicCallback( bool forceRound) { auto result = PeerListRow::generatePaintUserpicCallback(forceRound); - if (const auto st = _lockedSt) { + if (const auto &r = _restriction) { return [=](Painter &p, int x, int y, int outerWidth, int size) { result(p, x, y, outerWidth, size); - PaintPremiumRequiredLock(p, st, x, y, outerWidth, size); + PaintRestrictionBadge( + p, + _maybeLockedSt, + r->value.starsPerMessage, + r->cache, + x, + y, + outerWidth, + size); }; } return result; @@ -305,12 +334,14 @@ PaintRoundImageCallback RecipientRow::generatePaintUserpicCallback( bool RecipientRow::refreshLock( not_null maybeLockedSt) { if (const auto user = peer()->asUser()) { - const auto locked = _resolvePremiumRequired - && Api::ResolveMessageMoneyRestrictions( + using Restriction = Api::MessageMoneyRestriction; + const auto r = _maybeLockedSt + ? Api::ResolveMessageMoneyRestrictions( user, - _maybeHistory).premiumRequired; - if (this->locked() != locked) { - setLocked(locked ? maybeLockedSt.get() : nullptr); + _maybeHistory) + : Restriction(); + if ((_restriction ? _restriction->value : Restriction()) != r) { + setRestriction(r); return true; } } @@ -320,14 +351,20 @@ bool RecipientRow::refreshLock( void RecipientRow::preloadUserpic() { PeerListRow::preloadUserpic(); - if (!_resolvePremiumRequired) { + if (!_maybeLockedSt) { return; - } else if (!Api::ResolveMessageMoneyRestrictions( - peer(), - _maybeHistory).known) { - const auto user = peer()->asUser(); - user->session().api().premium().resolveMessageMoneyRestrictions( - user); + } + const auto peer = this->peer(); + const auto known = Api::ResolveMessageMoneyRestrictions( + peer, + _maybeHistory).known; + if (known) { + return; + } else if (const auto user = peer->asUser()) { + const auto api = &user->session().api(); + api->premium().resolveMessageMoneyRestrictions(user); + } else if (const auto group = peer->asChannel()) { + group->updateFull(); } } @@ -844,7 +881,8 @@ bool RecipientRow::ShowLockedError( not_null controller, not_null row, Fn)> error) { - if (!static_cast(row.get())->locked()) { + const auto recipient = static_cast(row.get()); + if (!recipient->restriction().premiumRequired) { return false; } ::Settings::ShowPremiumPromoToast( @@ -1100,25 +1138,61 @@ auto ChooseTopicBoxController::createRow(not_null topic) return skip ? nullptr : std::make_unique(topic); }; -void PaintPremiumRequiredLock( +void PaintRestrictionBadge( Painter &p, not_null st, + int stars, + RestrictionBadgeCache &cache, int x, int y, int outerWidth, int size) { - auto hq = PainterHighQualityEnabler(p); + const auto paletteVersion = style::PaletteVersion(); + const auto good = !cache.badge.isNull() + && (cache.stars == stars) + && (cache.paletteVersion == paletteVersion); const auto &check = st->checkbox.check; - auto pen = check.border->p; - pen.setWidthF(check.width); - p.setPen(pen); - p.setBrush(st::premiumButtonBg2); - const auto &icon = st::stickersPremiumLock; - const auto width = icon.width(); - const auto height = icon.height(); - const auto rect = QRect( - QPoint(x + size - width, y + size - height), - icon.size()); - p.drawEllipse(rect); - icon.paintInCenter(p, rect); + const auto add = check.width; + if (!good) { + cache.stars = stars; + cache.paletteVersion = paletteVersion; + if (stars) { + const auto text = (stars >= 1000) + ? (QString::number(stars / 1000) + 'K') + : QString::number(stars); + cache.badge = Ui::GenerateSmallBadgeImage( + text, + st::paidReactTopStarIcon, + check.bgActive->c, + st::premiumButtonFg->c, + &check); + } else { + auto hq = PainterHighQualityEnabler(p); + const auto &icon = st::stickersPremiumLock; + const auto width = icon.width(); + const auto height = icon.height(); + const auto rect = QRect( + QPoint(x + size - width, y + size - height), + icon.size()); + const auto added = QMargins(add, add, add, add); + const auto ratio = style::DevicePixelRatio(); + cache.badge = QImage( + (rect + added).size() * ratio, + QImage::Format_ARGB32_Premultiplied); + cache.badge.setDevicePixelRatio(ratio); + cache.badge.fill(Qt::transparent); + const auto inner = QRect(add, add, rect.width(), rect.height()); + auto q = QPainter(&cache.badge); + auto pen = check.border->p; + pen.setWidthF(check.width); + q.setPen(pen); + q.setBrush(st::premiumButtonBg2); + q.drawEllipse(inner); + icon.paintInCenter(q, inner); + } + } + const auto cached = cache.badge.size() / cache.badge.devicePixelRatio(); + const auto left = x + size + add - cached.width(); + const auto top = stars ? (y - add) : (y + size + add - cached.height()); + p.drawImage(left, top, cache.badge); } diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.h b/Telegram/SourceFiles/boxes/peer_list_controllers.h index 9bec5a98e..bd97a408b 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.h +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h @@ -19,6 +19,10 @@ namespace style { struct PeerListItem; } // namespace style +namespace Api { +struct MessageMoneyRestriction; +} // namespace Api + namespace Data { class Thread; class Forum; @@ -100,6 +104,21 @@ struct RecipientMoneyRestrictionError { [[nodiscard]] RecipientMoneyRestrictionError WriteMoneyRestrictionError( not_null user); +struct RestrictionBadgeCache { + int paletteVersion = 0; + int stars = 0; + QImage badge; +}; +void PaintRestrictionBadge( + Painter &p, + not_null st, + int stars, + RestrictionBadgeCache &cache, + int x, + int y, + int outerWidth, + int size); + class RecipientRow : public PeerListRow { public: explicit RecipientRow( @@ -117,21 +136,20 @@ public: [[nodiscard]] History *maybeHistory() const { return _maybeHistory; } - [[nodiscard]] bool locked() const { - return _lockedSt != nullptr; - } - void setLocked(const style::PeerListItem *lockedSt) { - _lockedSt = lockedSt; - } PaintRoundImageCallback generatePaintUserpicCallback( bool forceRound) override; void preloadUserpic() override; + [[nodiscard]] Api::MessageMoneyRestriction restriction() const; + void setRestriction(Api::MessageMoneyRestriction restriction); + private: + struct Restriction; + History *_maybeHistory = nullptr; - const style::PeerListItem *_lockedSt = nullptr; - bool _resolvePremiumRequired = false; + const style::PeerListItem *_maybeLockedSt = nullptr; + std::shared_ptr _restriction; }; @@ -371,11 +389,3 @@ private: Fn)> _filter; }; - -void PaintPremiumRequiredLock( - Painter &p, - not_null st, - int x, - int y, - int outerWidth, - int size); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp index f37bda892..288a09353 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp @@ -1486,6 +1486,7 @@ object_ptr ShareInviteLinkBox( }; auto submitCallback = [=]( std::vector> &&result, + Fn checkPaid, TextWithTags &&comment, Api::SendOptions options, Data::ForwardOptions) { @@ -1503,6 +1504,8 @@ object_ptr ShareInviteLinkBox( result.size() > 1)); } return; + } else if (!checkPaid(1)) { + return; } *sending = true; diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 3a890a91f..ff6733f22 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -118,12 +118,13 @@ private: Ui::RoundImageCheckbox checkbox; Ui::Text::String name; Ui::Animations::Simple nameActive; - bool locked = false; + Api::MessageMoneyRestriction restriction; + RestrictionBadgeCache badgeCache; }; void invalidateCache(); bool showLockedError(not_null chat); - void refreshLockedRows(); + void refreshRestrictedRows(); [[nodiscard]] int displayedChatsCount() const; [[nodiscard]] not_null chatThread( @@ -132,7 +133,7 @@ private: void paintChat(Painter &p, not_null chat, int index); void updateChat(not_null peer); void updateChatName(not_null chat); - void initChatLocked(not_null chat); + void initChatRestriction(not_null chat); void repaintChat(not_null peer); int chatIndex(not_null peer) const; void repaintChatAtIndex(int index); @@ -652,6 +653,67 @@ void ShareBox::innerSelectedChanged( } void ShareBox::submit(Api::SendOptions options) { + _submitLifetime.destroy(); + + auto threads = _inner->selected(); + const auto weak = Ui::MakeWeak(this); + const auto checkPaid = [=](int messagesCount) { + const auto withPaymentApproved = crl::guard(weak, [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + submit(copy); + }); + + const auto alreadyApproved = options.starsApproved; + auto paid = std::vector>(); + auto waiting = base::flat_set>(); + auto totalStars = 0; + for (const auto &thread : threads) { + const auto peer = thread->peer(); + const auto details = ComputePaymentDetails(peer, messagesCount); + if (!details) { + waiting.emplace(peer); + } else if (details->stars > 0) { + totalStars += details->stars; + paid.push_back(thread); + } + } + if (!waiting.empty()) { + _descriptor.session->changes().peerUpdates( + Data::PeerUpdate::Flag::FullInfo + ) | rpl::start_with_next([=](const Data::PeerUpdate &update) { + if (waiting.contains(update.peer)) { + withPaymentApproved(alreadyApproved); + } + }, _submitLifetime); + + if (!_descriptor.session->credits().loaded()) { + _descriptor.session->credits().loadedValue( + ) | rpl::filter( + rpl::mappers::_1 + ) | rpl::take(1) | rpl::start_with_next([=] { + withPaymentApproved(alreadyApproved); + }, _submitLifetime); + } + return false; + } else if (totalStars > alreadyApproved) { + const auto show = uiShow(); + const auto session = _descriptor.session; + const auto sessionShow = Main::MakeSessionShow(show, session); + const auto scheduleBoxSt = _descriptor.st.scheduleBox.get(); + ShowSendPaidConfirm(sessionShow, paid, SendPaymentDetails{ + .messages = messagesCount, + .stars = totalStars, + }, [=] { withPaymentApproved(totalStars); }, PaidConfirmStyles{ + .label = (scheduleBoxSt + ? scheduleBoxSt->chooseDateTimeArgs.labelStyle + : nullptr), + .checkbox = _descriptor.st.checkbox, + }); + return false; + } + return true; + }; if (const auto onstack = _descriptor.submitCallback) { const auto forwardOptions = (_forwardOptions.captionsCount && _forwardOptions.dropCaptions) @@ -660,7 +722,8 @@ void ShareBox::submit(Api::SendOptions options) { ? Data::ForwardOptions::NoSenderNames : Data::ForwardOptions::PreserveInfo; onstack( - _inner->selected(), + std::move(threads), + checkPaid, _comment->entity()->getTextWithAppliedMarkdown(), options, forwardOptions); @@ -727,7 +790,7 @@ ShareBox::Inner::Inner( Data::AmPremiumValue(session) | rpl::to_empty, session->api().premium().someMessageMoneyRestrictionsResolved() ) | rpl::start_with_next([=] { - refreshLockedRows(); + refreshRestrictedRows(); }, lifetime()); } @@ -788,7 +851,7 @@ void ShareBox::Inner::invalidateCache() { } bool ShareBox::Inner::showLockedError(not_null chat) { - if (!chat->locked) { + if (!chat->restriction.premiumRequired) { return false; } ::Settings::ShowPremiumPromoToast( @@ -799,25 +862,25 @@ bool ShareBox::Inner::showLockedError(not_null chat) { return true; } -void ShareBox::Inner::refreshLockedRows() { +void ShareBox::Inner::refreshRestrictedRows() { auto changed = false; for (const auto &[peer, data] : _dataMap) { const auto history = data->history; - const auto locked = Api::ResolveMessageMoneyRestrictions( + const auto restriction = Api::ResolveMessageMoneyRestrictions( history->peer, - history).premiumRequired; - if (data->locked != locked) { - data->locked = locked; + history); + if (data->restriction != restriction) { + data->restriction = restriction; changed = true; } } for (const auto &data : d_byUsernameFiltered) { const auto history = data->history; - const auto locked = Api::ResolveMessageMoneyRestrictions( + const auto restriction = Api::ResolveMessageMoneyRestrictions( history->peer, - history).premiumRequired; - if (data->locked != locked) { - data->locked = locked; + history); + if (data->restriction != restriction) { + data->restriction = restriction; changed = true; } } @@ -884,13 +947,14 @@ void ShareBox::Inner::updateChatName(not_null chat) { chat->name.setText(_st.item.nameStyle, text, Ui::NameTextOptions()); } -void ShareBox::Inner::initChatLocked(not_null chat) { +void ShareBox::Inner::initChatRestriction(not_null chat) { if (_descriptor.moneyRestrictionError) { const auto history = chat->history; - if (Api::ResolveMessageMoneyRestrictions( - history->peer, - history).premiumRequired) { - chat->locked = true; + const auto restriction = Api::ResolveMessageMoneyRestrictions( + history->peer, + history); + if (restriction) { + chat->restriction = restriction; } } } @@ -1021,9 +1085,10 @@ void ShareBox::Inner::preloadUserpic(not_null entry) { } else if (!Api::ResolveMessageMoneyRestrictions( history->peer, history).known) { - const auto user = history->peer->asUser(); - _descriptor.session->api().premium().resolveMessageMoneyRestrictions( - user); + if (const auto user = history->peer->asUser()) { + const auto api = &_descriptor.session->api(); + api->premium().resolveMessageMoneyRestrictions(user); + } } } @@ -1046,7 +1111,7 @@ auto ShareBox::Inner::getChat(not_null row) repaintChat(peer); })); updateChatName(i->second.get()); - initChatLocked(i->second.get()); + initChatRestriction(i->second.get()); row->attached = i->second.get(); return i->second.get(); } @@ -1080,10 +1145,12 @@ void ShareBox::Inner::paintChat( auto photoTop = st::sharePhotoTop; chat->checkbox.paint(p, x + photoLeft, y + photoTop, outerWidth); - if (chat->locked) { - PaintPremiumRequiredLock( + if (chat->restriction) { + PaintRestrictionBadge( p, &_st.item, + chat->restriction.starsPerMessage, + chat->badgeCache, x + photoLeft, y + photoTop, outerWidth, @@ -1438,7 +1505,7 @@ void ShareBox::Inner::peopleReceived( _st.item, [=] { repaintChat(peer); })); updateChatName(d_byUsernameFiltered.back().get()); - initChatLocked(d_byUsernameFiltered.back().get()); + initChatRestriction(d_byUsernameFiltered.back().get()); } } }; @@ -1502,24 +1569,29 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( const auto state = std::make_shared(); return [=]( std::vector> &&result, - TextWithTags &&comment, + Fn checkPaid, + TextWithTags comment, Api::SendOptions options, Data::ForwardOptions forwardOptions) { if (!state->requests.empty()) { return; // Share clicked already. } + const auto items = history->owner().idsToItems(msgIds); const auto existingIds = history->owner().itemsToIds(items); if (existingIds.empty() || result.empty()) { return; } + auto messagesCount = int(items.size()) + (comment.empty() ? 0 : 1); const auto error = GetErrorForSending( result, { .forward = &items, .text = &comment }); if (error.error) { show->showBox(MakeSendErrorBox(error, result.size() > 1)); return; + } else if (!checkPaid(messagesCount)) { + return; } using Flag = MTPmessages_ForwardMessages::Flag; @@ -1614,7 +1686,11 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( } finish(); }).fail([=](const MTP::Error &error) { - if (error.type() == u"VOICE_MESSAGES_FORBIDDEN"_q) { + const auto type = error.type(); + if (type.startsWith(u"ALLOW_PAYMENT_REQUIRED_"_q)) { + show->showToast(u"Payment requirements changed. " + "Please, try again."_q); + } else if (type == u"VOICE_MESSAGES_FORBIDDEN"_q) { show->showToast( tr::lng_restricted_send_voice_messages( tr::now, @@ -1653,6 +1729,7 @@ ShareBoxStyleOverrides DarkShareBoxStyle() { .comment = &st::groupCallShareBoxComment, .peerList = &st::groupCallShareBoxList, .label = &st::groupCallField, + .checkbox = &st::groupCallCheckbox, .scheduleBox = std::make_shared(schedule()), }; } @@ -1765,6 +1842,7 @@ void FastShareLink( }; auto submitCallback = [=]( std::vector> &&result, + Fn checkPaid, TextWithTags &&comment, Api::SendOptions options, ::Data::ForwardOptions) { @@ -1781,6 +1859,8 @@ void FastShareLink( MakeSendErrorBox(error, result.size() > 1)); } return; + } else if (!checkPaid(1)) { + return; } *sending = true; diff --git a/Telegram/SourceFiles/boxes/share_box.h b/Telegram/SourceFiles/boxes/share_box.h index 83cdbcb4f..07227ee98 100644 --- a/Telegram/SourceFiles/boxes/share_box.h +++ b/Telegram/SourceFiles/boxes/share_box.h @@ -66,6 +66,7 @@ struct ShareBoxStyleOverrides { const style::InputField *comment = nullptr; const style::PeerList *peerList = nullptr; const style::InputField *label = nullptr; + const style::Checkbox *checkbox = nullptr; std::shared_ptr scheduleBox; }; [[nodiscard]] ShareBoxStyleOverrides DarkShareBoxStyle(); @@ -96,6 +97,7 @@ public: using CopyCallback = Fn; using SubmitCallback = Fn>&&, + Fn checkPaid, TextWithTags&&, Api::SendOptions, Data::ForwardOptions)>; @@ -196,5 +198,6 @@ private: PeopleQueries _peopleQueries; Ui::Animations::Simple _scrollAnimation; + rpl::lifetime _submitLifetime; }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp index 672d6d4a1..dec18b742 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp @@ -134,6 +134,7 @@ object_ptr ShareInviteLinkBox( }; auto submitCallback = [=]( std::vector> &&result, + Fn checkPaid, TextWithTags &&comment, Api::SendOptions options, Data::ForwardOptions) { @@ -150,6 +151,8 @@ object_ptr ShareInviteLinkBox( MakeSendErrorBox(error, result.size() > 1)); } return; + } else if (!checkPaid(1)) { + return; } *sending = true; diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 64db33d13..209dab9f5 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -1747,7 +1747,8 @@ void InitFieldAutocomplete( && peer->isUser() && !peer->asUser()->isBot() && (!shortcutMessages - || shortcutMessages->shortcuts().list.empty())) { + || shortcutMessages->shortcuts().list.empty() + || peer->starsPerMessageChecked() != 0)) { parsed = {}; } raw->showFiltered(peer, parsed.query, parsed.fromStart); diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 5fb0d9f76..4d15bf4ca 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -249,19 +249,42 @@ void ShowSendPaidConfirm( not_null navigation, not_null peer, SendPaymentDetails details, - Fn confirmed) { + Fn confirmed, + PaidConfirmStyles styles) { return ShowSendPaidConfirm( navigation->uiShow(), peer, details, - confirmed); + confirmed, + styles); } void ShowSendPaidConfirm( std::shared_ptr show, not_null peer, SendPaymentDetails details, - Fn confirmed) { + Fn confirmed, + PaidConfirmStyles styles) { + ShowSendPaidConfirm( + std::move(show), + std::vector>{ peer->owner().history(peer) }, + details, + confirmed, + styles); +} + +void ShowSendPaidConfirm( + std::shared_ptr show, + const std::vector> &threads, + SendPaymentDetails details, + Fn confirmed, + PaidConfirmStyles styles) { + Expects(!threads.empty()); + + const auto singlePeer = (threads.size() > 1) + ? (PeerData*)nullptr + : threads.front()->peer().get(); + const auto recipientId = singlePeer ? singlePeer->id : PeerId(); const auto check = [=] { const auto required = details.stars; if (!required) { @@ -276,57 +299,81 @@ void ShowSendPaidConfirm( Settings::MaybeRequestBalanceIncrease( show, required, - Settings::SmallBalanceForMessage{ .recipientId = peer->id }, + Settings::SmallBalanceForMessage{ .recipientId = recipientId }, done); }; - - const auto session = &peer->session(); - if (session->local().isPeerTrustedPayForMessage(peer->id)) { - check(); - return; + auto usersOnly = true; + for (const auto &thread : threads) { + if (!thread->peer()->isUser()) { + usersOnly = false; + break; + } + } + if (singlePeer) { + const auto session = &singlePeer->session(); + if (session->local().isPeerTrustedPayForMessage(recipientId)) { + check(); + return; + } } const auto messages = details.messages; const auto stars = details.stars; show->showBox(Box([=](not_null box) { const auto trust = std::make_shared>(); const auto proceed = [=](Fn close) { - if ((*trust)->checked()) { - session->local().markPeerTrustedPayForMessage(peer->id); + if (singlePeer && (*trust)->checked()) { + const auto session = &singlePeer->session(); + session->local().markPeerTrustedPayForMessage(recipientId); } check(); close(); }; Ui::ConfirmBox(box, { - .text = tr::lng_payment_confirm_text( - tr::now, - lt_count, - stars / messages, - lt_name, - Ui::Text::Bold(peer->shortName()), - Ui::Text::RichLangValue).append(' ').append( - tr::lng_payment_confirm_sure( + .text = (singlePeer + ? tr::lng_payment_confirm_text( + tr::now, + lt_count, + stars / messages, + lt_name, + Ui::Text::Bold(singlePeer->shortName()), + Ui::Text::RichLangValue) + : (usersOnly + ? tr::lng_payment_confirm_users + : tr::lng_payment_confirm_chats)( tr::now, lt_count, - messages, - lt_amount, - tr::lng_payment_confirm_amount( - tr::now, - lt_count, - stars, - Ui::Text::RichLangValue), - Ui::Text::RichLangValue)), + int(threads.size()), + Ui::Text::RichLangValue)).append(' ').append( + tr::lng_payment_confirm_sure( + tr::now, + lt_count, + messages, + lt_amount, + tr::lng_payment_confirm_amount( + tr::now, + lt_count, + stars, + Ui::Text::RichLangValue), + Ui::Text::RichLangValue)), .confirmed = proceed, .confirmText = tr::lng_payment_confirm_button( lt_count, rpl::single(messages * 1.)), + .labelStyle = styles.label, .title = tr::lng_payment_confirm_title(), }); - const auto skip = st::defaultCheckbox.margin.top(); - *trust = box->addRow( - object_ptr( - box, - tr::lng_payment_confirm_dont_ask(tr::now)), - st::boxRowPadding + QMargins(0, skip, 0, skip)); + if (singlePeer) { + const auto skip = st::defaultCheckbox.margin.top(); + *trust = box->addRow( + object_ptr( + box, + tr::lng_payment_confirm_dont_ask(tr::now), + false, + (styles.checkbox + ? *styles.checkbox + : st::defaultCheckbox)), + st::boxRowPadding + QMargins(0, skip, 0, skip)); + } })); } diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index 34d8f5d18..4106df38f 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -11,6 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; +namespace style { +struct FlatLabel; +struct Checkbox; +} // namespace style + namespace Api { struct SendOptions; struct SendAction; @@ -144,16 +149,28 @@ struct SendPaymentDetails { not_null peer, int messagesCount); +struct PaidConfirmStyles { + const style::FlatLabel *label = nullptr; + const style::Checkbox *checkbox = nullptr; +}; void ShowSendPaidConfirm( not_null navigation, not_null peer, SendPaymentDetails details, - Fn confirmed); + Fn confirmed, + PaidConfirmStyles styles = {}); void ShowSendPaidConfirm( std::shared_ptr show, not_null peer, SendPaymentDetails details, - Fn confirmed); + Fn confirmed, + PaidConfirmStyles styles = {}); +void ShowSendPaidConfirm( + std::shared_ptr show, + const std::vector> &threads, + SendPaymentDetails details, + Fn confirmed, + PaidConfirmStyles styles = {}); class SendPaymentHelper final { public: diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 9609054cc..5ce0d2352 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -4575,26 +4575,6 @@ void HistoryWidget::reportSelectedMessages() { } } -void HistoryWidget::payForMessageSure(bool trust) { - const auto required = _peer->starsPerMessage(); - if (!required) { - return; - } - const auto done = [=](Settings::SmallBalanceResult result) { - if (result == Settings::SmallBalanceResult::Success - || result == Settings::SmallBalanceResult::Already) { - if (canWriteMessage()) { - setInnerFocus(); - } - } - }; - Settings::MaybeRequestBalanceIncrease( - controller()->uiShow(), - required, - Settings::SmallBalanceForMessage{ .recipientId = _peer->id }, - crl::guard(this, done)); -} - History *HistoryWidget::history() const { return _history; } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 425419530..f52dd3086 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -426,7 +426,6 @@ private: [[nodiscard]] int computeMaxFieldHeight() const; void toggleMuteUnmute(); void reportSelectedMessages(); - void payForMessageSure(bool trust = false); void showKeyboardHideButton(); void toggleKeyboard(bool manual = true); void startBotCommand(); diff --git a/Telegram/SourceFiles/media/stories/media_stories_share.cpp b/Telegram/SourceFiles/media/stories/media_stories_share.cpp index 90f6ca1e2..d00da018c 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_share.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_share.cpp @@ -78,6 +78,7 @@ namespace Media::Stories { : Fn(); auto submitCallback = [=]( std::vector> &&result, + Fn checkPaid, TextWithTags &&comment, Api::SendOptions options, Data::ForwardOptions forwardOptions) { @@ -95,6 +96,8 @@ namespace Media::Stories { if (error.error) { show->showBox(MakeSendErrorBox(error, result.size() > 1)); return; + } else if (!checkPaid(comment.text.isEmpty() ? 1 : 2)) { + return; } const auto api = &story->owner().session().api(); @@ -133,7 +136,7 @@ namespace Media::Stories { sendFlags |= SendFlag::f_invert_media; } const auto starsPaid = std::min( - peer->starsPerMessageChecked(), + threadHistory->peer->starsPerMessageChecked(), options.starsApproved); if (starsPaid) { options.starsApproved -= starsPaid; diff --git a/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp b/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp index 7cc513f7b..1786d250c 100644 --- a/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp @@ -147,53 +147,11 @@ void PaidReactionSlider( } [[nodiscard]] QImage GenerateBadgeImage(int count) { - const auto text = Lang::FormatCountDecimal(count); - const auto length = st::chatSimilarBadgeFont->width(text); - const auto contents = st::chatSimilarLockedIconPosition.x() - + st::paidReactTopStarIcon.width() - + st::paidReactTopStarSkip - + length; - const auto badge = QRect( - st::chatSimilarBadgePadding.left(), - st::chatSimilarBadgePadding.top(), - contents, - st::chatSimilarBadgeFont->height); - const auto rect = badge.marginsAdded(st::chatSimilarBadgePadding); - - auto result = QImage( - rect.size() * style::DevicePixelRatio(), - QImage::Format_ARGB32_Premultiplied); - result.setDevicePixelRatio(style::DevicePixelRatio()); - result.fill(Qt::transparent); - auto q = QPainter(&result); - - const auto &font = st::chatSimilarBadgeFont; - const auto textTop = badge.y() + font->ascent; - const auto icon = &st::paidReactTopStarIcon; - const auto position = st::chatSimilarLockedIconPosition; - - auto hq = PainterHighQualityEnabler(q); - q.setBrush(st::creditsBg3); - q.setPen(Qt::NoPen); - const auto radius = rect.height() / 2.; - q.drawRoundedRect(rect, radius, radius); - - auto textLeft = 0; - if (icon) { - icon->paint( - q, - badge.x() + position.x(), - badge.y() + position.y(), - rect.width()); - textLeft += position.x() + icon->width() + st::paidReactTopStarSkip; - } - - q.setFont(font); - q.setPen(st::premiumButtonFg); - q.drawText(textLeft, textTop, text); - q.end(); - - return result; + return GenerateSmallBadgeImage( + Lang::FormatCountDecimal(count), + st::paidReactTopStarIcon, + st::creditsBg3->c, + st::premiumButtonFg->c); } void AddArrowDown(not_null widget) { @@ -321,7 +279,6 @@ void SelectShownPeer( updateUserpic(); } (*menu)->popup(QCursor::pos()); - } void FillTopReactors( @@ -633,4 +590,65 @@ object_ptr MakePaidReactionBox(PaidReactionBoxArgs &&args) { return Box(PaidReactionsBox, std::move(args)); } +QImage GenerateSmallBadgeImage( + QString text, + const style::icon &icon, + QColor bg, + QColor fg, + const style::RoundCheckbox *borderSt) { + const auto length = st::chatSimilarBadgeFont->width(text); + const auto contents = st::chatSimilarLockedIconPosition.x() + + icon.width() + + st::paidReactTopStarSkip + + length; + const auto badge = QRect( + st::chatSimilarBadgePadding.left(), + st::chatSimilarBadgePadding.top(), + contents, + st::chatSimilarBadgeFont->height); + const auto rect = badge.marginsAdded(st::chatSimilarBadgePadding); + const auto add = borderSt ? borderSt->width : 0; + const auto ratio = style::DevicePixelRatio(); + auto result = QImage( + (rect + QMargins(add, add, add, add)).size() * ratio, + QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(ratio); + result.fill(Qt::transparent); + auto q = QPainter(&result); + + const auto &font = st::chatSimilarBadgeFont; + const auto textTop = badge.y() + font->ascent; + const auto position = st::chatSimilarLockedIconPosition; + + auto hq = PainterHighQualityEnabler(q); + q.translate(add, add); + q.setBrush(bg); + if (borderSt) { + q.setPen(QPen(borderSt->border->c, borderSt->width)); + } else { + q.setPen(Qt::NoPen); + } + const auto radius = rect.height() / 2.; + const auto shift = add / 2.; + q.drawRoundedRect( + QRectF(rect) + QMarginsF(shift, shift, shift, shift), + radius, + radius); + + auto textLeft = 0; + icon.paint( + q, + badge.x() + position.x(), + badge.y() + position.y(), + rect.width()); + textLeft += position.x() + icon.width() + st::paidReactTopStarSkip; + + q.setFont(font); + q.setPen(fg); + q.drawText(textLeft, textTop, text); + q.end(); + + return result; +} + } // namespace Ui diff --git a/Telegram/SourceFiles/payments/ui/payments_reaction_box.h b/Telegram/SourceFiles/payments/ui/payments_reaction_box.h index 034b04289..34f67e066 100644 --- a/Telegram/SourceFiles/payments/ui/payments_reaction_box.h +++ b/Telegram/SourceFiles/payments/ui/payments_reaction_box.h @@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/object_ptr.h" +namespace style { +struct RoundCheckbox; +} // namespace style + namespace Ui { class BoxContent; @@ -48,4 +52,11 @@ void PaidReactionsBox( [[nodiscard]] object_ptr MakePaidReactionBox( PaidReactionBoxArgs &&args); +[[nodiscard]] QImage GenerateSmallBadgeImage( + QString text, + const style::icon &icon, + QColor bg, + QColor fg, + const style::RoundCheckbox *borderSt = nullptr); + } // namespace Ui diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 3e54ba9cf..62075809f 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -2091,7 +2091,9 @@ void SmallBalanceBox( }, [&](SmallBalanceStarGift value) { return owner->peer(value.recipientId)->shortName(); }, [&](SmallBalanceForMessage value) { - return owner->peer(value.recipientId)->shortName(); + return value.recipientId + ? owner->peer(value.recipientId)->shortName() + : QString(); }); auto needed = show->session().credits().balanceValue( @@ -2131,10 +2133,13 @@ void SmallBalanceBox( rpl::single(Ui::Text::Bold(name)), Ui::Text::RichLangValue) : v::is(source) - ? tr::lng_credits_small_balance_for_message( - lt_user, - rpl::single(Ui::Text::Bold(name)), - Ui::Text::RichLangValue) + ? (name.isEmpty() + ? tr::lng_credits_small_balance_for_messages( + Ui::Text::RichLangValue) + : tr::lng_credits_small_balance_for_message( + lt_user, + rpl::single(Ui::Text::Bold(name)), + Ui::Text::RichLangValue)) : name.isEmpty() ? tr::lng_credits_small_balance_fallback( Ui::Text::RichLangValue) diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 8854d89a0..09193517b 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -2409,6 +2409,7 @@ QPointer ShowForwardMessagesBox( not_null peer) -> Controller::Chosen { return peer->owner().history(peer); }) | ranges::to_vector, + [](int messagesCount) { return true; }, comment->entity()->getTextWithAppliedMarkdown(), options, state->box->forwardOptionsData()); From ee9d0cfd99c529cdf27a01869f60b8ac6a433fda Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 25 Feb 2025 11:36:51 +0400 Subject: [PATCH 059/103] Update API scheme, disable scheduled paid. --- Telegram/SourceFiles/boxes/share_box.cpp | 16 +++++++++++--- Telegram/SourceFiles/core/ui_integration.cpp | 4 ++++ .../SourceFiles/history/history_widget.cpp | 3 +++ .../history/view/history_view_bottom_info.cpp | 22 +++++++++++++++++-- .../history/view/history_view_bottom_info.h | 1 + .../view/history_view_context_menu.cpp | 2 ++ .../history/view/history_view_message.cpp | 3 +++ .../view/history_view_replies_section.cpp | 4 +++- .../view/history_view_schedule_box.cpp | 3 ++- .../inline_bots/bot_attach_web_view.cpp | 6 +++-- Telegram/SourceFiles/mtproto/scheme/api.tl | 9 ++++---- Telegram/SourceFiles/ui/effects/credits.style | 4 ++++ .../SourceFiles/window/window_peer_menu.cpp | 15 +++++++++++-- Telegram/lib_ui | 2 +- 14 files changed, 78 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index ff6733f22..f5abb2e86 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -513,9 +513,19 @@ void ShareBox::keyPressEvent(QKeyEvent *e) { SendMenu::Details ShareBox::sendMenuDetails() const { const auto selected = _inner->selected(); - const auto type = ranges::all_of( - selected | ranges::views::transform(&Data::Thread::peer), - HistoryView::CanScheduleUntilOnline) + const auto hasPaid = [&] { + for (const auto &thread : selected) { + if (thread->peer()->starsPerMessageChecked()) { + return true; + } + } + return false; + }(); + const auto type = hasPaid + ? SendMenu::Type::SilentOnly + : ranges::all_of( + selected | ranges::views::transform(&Data::Thread::peer), + HistoryView::CanScheduleUntilOnline) ? SendMenu::Type::ScheduledToUser : (selected.size() == 1 && selected.front()->peer()->isSelf()) ? SendMenu::Type::Reminder diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp index 40604a6c4..48325cafb 100644 --- a/Telegram/SourceFiles/core/ui_integration.cpp +++ b/Telegram/SourceFiles/core/ui_integration.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "iv/iv_instance.h" #include "ui/text/text_custom_emoji.h" +#include "ui/text/text_utilities.h" #include "ui/basic_click_handlers.h" #include "ui/emoji_config.h" #include "lang/lang_keys.h" @@ -275,6 +276,9 @@ bool UiIntegration::copyPreOnClick(const QVariant &context) { std::unique_ptr UiIntegration::createCustomEmoji( QStringView data, const std::any &context) { + if (auto simple = Ui::Text::TryMakeSimpleEmoji(data)) { + return simple; + } const auto my = std::any_cast(&context); if (!my || !my->session) { return nullptr; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 5ce0d2352..e72412884 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -547,6 +547,7 @@ HistoryWidget::HistoryWidget( session().attachWebView().attachBotsUpdates(), session().changes().peerUpdates( Data::PeerUpdate::Flag::Rights + | Data::PeerUpdate::Flag::StarsPerMessage ) | rpl::filter([=](const Data::PeerUpdate &update) { return update.peer == _peer; }) | rpl::to_empty @@ -4470,6 +4471,8 @@ void HistoryWidget::sendScheduled(Api::SendOptions initialOptions) { SendMenu::Details HistoryWidget::sendMenuDetails() const { const auto type = !_peer ? SendMenu::Type::Disabled + : _peer->starsPerMessageChecked() + ? SendMenu::Type::SilentOnly : _peer->isSelf() ? SendMenu::Type::Reminder : HistoryView::CanScheduleUntilOnline(_peer) diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index 361e1ee51..b7d216b71 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_components.h" #include "history/history_item.h" #include "history/history.h" +#include "history/view/media/history_view_media.h" #include "history/view/history_view_message.h" #include "history/view/history_view_cursor_state.h" #include "chat_helpers/emoji_interactions.h" @@ -28,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_message_reactions.h" #include "window/window_session_controller.h" #include "styles/style_chat.h" +#include "styles/style_credits.h" #include "styles/style_dialogs.h" namespace HistoryView { @@ -431,9 +433,14 @@ void BottomInfo::layoutDateText() { : name.isEmpty() ? date : (name + afterAuthor); - _authorEditedDate.setText( + auto marked = TextWithEntities{ full }; + if (const auto count = _data.stars) { + marked.append(Ui::Text::IconEmoji(&st::starIconEmoji)); + marked.append(QString::number(count)); + } + _authorEditedDate.setMarkedText( st::msgDateTextStyle, - full, + marked, Ui::NameTextOptions()); } @@ -599,6 +606,17 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null message) { if (item->isSending() || item->hasFailed()) { result.flags |= Flag::Sending; } + if (item->out() && !item->history()->peer->isUser()) { + const auto media = message->media(); + const auto mine = PaidInformation{ + .messages = 1, + .stars = item->starsPaid(), + }; + auto info = media ? media->paidInformation().value_or(mine) : mine; + if (const auto total = info.stars) { + result.stars = total; + } + } const auto forwarded = item->Get(); if (forwarded && forwarded->imported) { result.flags |= Flag::Imported; diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.h b/Telegram/SourceFiles/history/view/history_view_bottom_info.h index 32e3e8fcd..218d047c9 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.h +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.h @@ -50,6 +50,7 @@ public: QDateTime date; QString author; EffectId effectId = 0; + int stars = 0; std::optional views; std::optional replies; std::optional forwardsCount; diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 678f59ac9..d1cead754 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -581,6 +581,8 @@ bool AddRescheduleAction( const auto peer = firstItem->history()->peer; const auto sendMenuType = !peer ? SendMenu::Type::Disabled + : peer->starsPerMessageChecked() + ? SendMenu::Type::SilentOnly : peer->isSelf() ? SendMenu::Type::Reminder : HistoryView::CanScheduleUntilOnline(peer) diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 9e28b6188..19d3ea733 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -444,6 +444,9 @@ Message::~Message() { void Message::initPaidInformation() { const auto item = data(); + if (!item->history()->peer->isUser()) { + return; + } const auto media = this->media(); const auto mine = PaidInformation{ .messages = 1, diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 3d64635cd..3ffc1348e 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -1507,7 +1507,9 @@ void RepliesWidget::sendInlineResult( SendMenu::Details RepliesWidget::sendMenuDetails() const { using Type = SendMenu::Type; - const auto type = _topic ? Type::Scheduled : Type::SilentOnly; + const auto type = (_topic && !_history->peer->starsPerMessageChecked()) + ? Type::Scheduled + : Type::SilentOnly; return SendMenu::Details{ .type = type }; } diff --git a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp index 9ab0af794..1dc8a8b64 100644 --- a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp +++ b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp @@ -62,7 +62,8 @@ bool CanScheduleUntilOnline(not_null peer) { if (const auto user = peer->asUser()) { return !user->isSelf() && !user->isBot() - && !user->lastseen().isHidden(); + && !user->lastseen().isHidden() + && !user->starsPerMessageChecked(); } return false; } diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 09a643580..a56cba037 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -2573,7 +2573,8 @@ std::unique_ptr MakeAttachBotsMenu( const auto source = action.options.scheduled ? Api::SendType::Scheduled : Api::SendType::Normal; - const auto sendMenuType = action.replyTo.topicRootId + const auto sendMenuType = (action.replyTo.topicRootId + || action.history->peer->starsPerMessageChecked()) ? SendMenu::Type::SilentOnly : SendMenu::Type::Scheduled; const auto flag = PollData::Flags(); @@ -2597,7 +2598,8 @@ std::unique_ptr MakeAttachBotsMenu( ChooseAndSendLocation(controller, config, actionFactory()); }, &st::menuIconAddress); } - const auto addBots = Data::CanSend(peer, ChatRestriction::SendInline); + const auto addBots = Data::CanSend(peer, ChatRestriction::SendInline) + && !peer->starsPerMessageChecked(); for (const auto &bot : bots->attachBots()) { if (!addBots || !bot.inAttachMenu diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 84522a174..a86e910d9 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -103,7 +103,7 @@ channel#7482147e flags:# creator:flags.0?true left:flags.2?true broadcast:flags. channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#2633421b flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions reactions_limit:flags.20?int = ChatFull; -channelFull#52d6806b flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int = ChatFull; +channelFull#52d6806b flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true paid_messages_available:flags2.20?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -220,7 +220,7 @@ inputPeerNotifySettings#cacb6ae2 flags:# show_previews:flags.0?Bool silent:flags peerNotifySettings#99622c0c flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int ios_sound:flags.3?NotificationSound android_sound:flags.4?NotificationSound other_sound:flags.5?NotificationSound stories_muted:flags.6?Bool stories_hide_sender:flags.7?Bool stories_ios_sound:flags.8?NotificationSound stories_android_sound:flags.9?NotificationSound stories_other_sound:flags.10?NotificationSound = PeerNotifySettings; -peerSettings#d8c39ec flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true business_bot_paused:flags.11?true business_bot_can_reply:flags.12?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int business_bot_id:flags.13?long business_bot_manage_url:flags.13?string charge_paid_message_stars:flags.14?long registration_month:flags.15?string phone_country:flags.16?string location_country:flags.17?string = PeerSettings; +peerSettings#f47741f7 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true business_bot_paused:flags.11?true business_bot_can_reply:flags.12?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int business_bot_id:flags.13?long business_bot_manage_url:flags.13?string charge_paid_message_stars:flags.14?long registration_month:flags.15?string phone_country:flags.16?string name_change_date:flags.17?int photo_change_date:flags.18?int = PeerSettings; wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper; wallPaperNoFile#e0804116 id:long flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper; @@ -1839,7 +1839,7 @@ starsTransactionPeerAPI#f9677aad = StarsTransactionPeer; starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; -starsTransaction#ecd50924 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true id:string stars:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount paid_messages:flags.19?int = StarsTransaction; +starsTransaction#ecd50924 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true premium_gift:flags.20?true id:string stars:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount paid_messages:flags.19?int = StarsTransaction; payments.starsStatus#6c9ce8ed flags:# balance:StarsAmount subscriptions:flags.1?Vector subscriptions_next_offset:flags.2?string subscriptions_missing_balance:flags.4?long history:flags.3?Vector next_offset:flags.0?string chats:Vector users:Vector = payments.StarsStatus; @@ -1926,7 +1926,7 @@ payments.uniqueStarGift#caa2f60b gift:StarGift users:Vector = payments.Uni messages.webPagePreview#b53e8b21 media:MessageMedia users:Vector = messages.WebPagePreview; -savedStarGift#6056dba5 flags:# name_hidden:flags.0?true unsaved:flags.5?true refunded:flags.9?true can_upgrade:flags.10?true from_id:flags.1?Peer date:int gift:StarGift message:flags.2?TextWithEntities msg_id:flags.3?int saved_id:flags.11?long convert_stars:flags.4?long upgrade_stars:flags.6?long can_export_at:flags.7?int transfer_stars:flags.8?long = SavedStarGift; +savedStarGift#6056dba5 flags:# name_hidden:flags.0?true unsaved:flags.5?true refunded:flags.9?true can_upgrade:flags.10?true pinned_to_top:flags.12?true from_id:flags.1?Peer date:int gift:StarGift message:flags.2?TextWithEntities msg_id:flags.3?int saved_id:flags.11?long convert_stars:flags.4?long upgrade_stars:flags.6?long can_export_at:flags.7?int transfer_stars:flags.8?long = SavedStarGift; payments.savedStarGifts#95f389b1 flags:# count:int chat_notifications_enabled:flags.1?Bool gifts:Vector next_offset:flags.0?string chats:Vector users:Vector = payments.SavedStarGifts; @@ -2543,6 +2543,7 @@ payments.getSavedStarGifts#23830de9 flags:# exclude_unsaved:flags.0?true exclude payments.getSavedStarGift#b455a106 stargift:Vector = payments.SavedStarGifts; payments.getStarGiftWithdrawalUrl#d06e93a8 stargift:InputSavedStarGift password:InputCheckPasswordSRP = payments.StarGiftWithdrawalUrl; payments.toggleChatStarGiftNotifications#60eaefa1 flags:# enabled:flags.0?true peer:InputPeer = Bool; +payments.toggleStarGiftsPinnedToTop#1513e7b0 peer:InputPeer stargift:Vector = Bool; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 36a5f24f6..2132c79f6 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -62,6 +62,10 @@ creditsBoxButtonLabel: FlatLabel(defaultFlatLabel) { style: semiboldTextStyle; } +starIconEmoji: IconEmoji { + icon: icon{{ "payments/small_star", windowFg }}; + padding: margins(0px, -3px, 0px, 0px); +} starIconSmall: icon{{ "payments/small_star", windowFg }}; starIconSmallPadding: margins(0px, -3px, 0px, 0px); diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 09193517b..606a547f0 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1175,7 +1175,8 @@ void Filler::addCreatePoll() { : Api::SendType::Normal; const auto sendMenuType = (_request.section == Section::Scheduled) ? SendMenu::Type::Disabled - : (_request.section == Section::Replies) + : (_request.section == Section::Replies + || _peer->starsPerMessageChecked()) ? SendMenu::Type::SilentOnly : SendMenu::Type::Scheduled; const auto flag = PollData::Flags(); @@ -2420,7 +2421,17 @@ QPointer ShowForwardMessagesBox( const auto sendMenuType = [=] { const auto selected = state->box->collectSelectedRows(); - return ranges::all_of(selected, HistoryView::CanScheduleUntilOnline) + const auto hasPaid = [&] { + for (const auto peer : selected) { + if (peer->starsPerMessageChecked()) { + return true; + } + } + return false; + }(); + return hasPaid + ? SendMenu::Type::SilentOnly + : ranges::all_of(selected, HistoryView::CanScheduleUntilOnline) ? SendMenu::Type::ScheduledToUser : ((selected.size() == 1) && selected.front()->isSelf()) ? SendMenu::Type::Reminder diff --git a/Telegram/lib_ui b/Telegram/lib_ui index f0cbee75e..2a5d66fb1 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit f0cbee75e49adfa2f3e0b7a76ac3290e1d388cbb +Subproject commit 2a5d66fb1b9f97eacc3e73c324944a8d77c38e51 From fe2df969538935b0fa1d7951b0bacbeecb8ff65c Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 25 Feb 2025 14:53:23 +0400 Subject: [PATCH 060/103] Improve paid peer-box multi-send. --- Telegram/SourceFiles/boxes/peer_list_box.cpp | 1 + Telegram/SourceFiles/boxes/peer_list_box.h | 7 + .../boxes/peer_list_controllers.cpp | 31 ++-- .../SourceFiles/boxes/peer_list_controllers.h | 8 +- Telegram/SourceFiles/boxes/share_box.cpp | 6 +- .../history/history_item_helpers.cpp | 16 +- .../history/history_item_helpers.h | 2 +- .../history/view/history_view_bottom_info.cpp | 2 +- .../inline_bots/bot_attach_web_view.cpp | 36 +++- .../SourceFiles/window/window_peer_menu.cpp | 155 ++++++++++++++++-- .../SourceFiles/window/window_peer_menu.h | 8 +- 11 files changed, 226 insertions(+), 46 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 8d4b60b1d..7a559faf3 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -873,6 +873,7 @@ void PeerListRow::paintUserpic( } else if (const auto callback = generatePaintUserpicCallback(false)) { callback(p, x, y, outerWidth, st.photoSize); } + paintUserpicOverlay(p, st, x, y, outerWidth); } // Emulates Ui::RoundImageCheckbox::paint() in a checked state. diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index c4a79c456..b8dbecb8c 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -95,6 +95,13 @@ public: [[nodiscard]] virtual QString generateShortName(); [[nodiscard]] virtual auto generatePaintUserpicCallback( bool forceRound) -> PaintRoundImageCallback; + virtual void paintUserpicOverlay( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int outerWidth) { + } [[nodiscard]] virtual auto generateNameFirstLetters() const -> const base::flat_set &; diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index f4f60bcc9..3b2214753 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -311,24 +311,23 @@ void RecipientRow::setRestriction(Api::MessageMoneyRestriction restriction) { _restriction->value = restriction; } -PaintRoundImageCallback RecipientRow::generatePaintUserpicCallback( - bool forceRound) { - auto result = PeerListRow::generatePaintUserpicCallback(forceRound); +void RecipientRow::paintUserpicOverlay( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int outerWidth) { if (const auto &r = _restriction) { - return [=](Painter &p, int x, int y, int outerWidth, int size) { - result(p, x, y, outerWidth, size); - PaintRestrictionBadge( - p, - _maybeLockedSt, - r->value.starsPerMessage, - r->cache, - x, - y, - outerWidth, - size); - }; + PaintRestrictionBadge( + p, + _maybeLockedSt, + r->value.starsPerMessage, + r->cache, + x, + y, + outerWidth, + st.photoSize); } - return result; } bool RecipientRow::refreshLock( diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.h b/Telegram/SourceFiles/boxes/peer_list_controllers.h index bd97a408b..de9c67dbf 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.h +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h @@ -136,8 +136,12 @@ public: [[nodiscard]] History *maybeHistory() const { return _maybeHistory; } - PaintRoundImageCallback generatePaintUserpicCallback( - bool forceRound) override; + void paintUserpicOverlay( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int outerWidth) override; void preloadUserpic() override; diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index f5abb2e86..e23dd7165 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -675,7 +675,7 @@ void ShareBox::submit(Api::SendOptions options) { }); const auto alreadyApproved = options.starsApproved; - auto paid = std::vector>(); + auto paid = std::vector>(); auto waiting = base::flat_set>(); auto totalStars = 0; for (const auto &thread : threads) { @@ -685,7 +685,7 @@ void ShareBox::submit(Api::SendOptions options) { waiting.emplace(peer); } else if (details->stars > 0) { totalStars += details->stars; - paid.push_back(thread); + paid.push_back(peer); } } if (!waiting.empty()) { @@ -963,7 +963,7 @@ void ShareBox::Inner::initChatRestriction(not_null chat) { const auto restriction = Api::ResolveMessageMoneyRestrictions( history->peer, history); - if (restriction) { + if (restriction || restriction.known) { chat->restriction = restriction; } } diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 4d15bf4ca..49fcebcb2 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -267,7 +267,7 @@ void ShowSendPaidConfirm( PaidConfirmStyles styles) { ShowSendPaidConfirm( std::move(show), - std::vector>{ peer->owner().history(peer) }, + std::vector>{ peer }, details, confirmed, styles); @@ -275,15 +275,15 @@ void ShowSendPaidConfirm( void ShowSendPaidConfirm( std::shared_ptr show, - const std::vector> &threads, + const std::vector> &peers, SendPaymentDetails details, Fn confirmed, PaidConfirmStyles styles) { - Expects(!threads.empty()); + Expects(!peers.empty()); - const auto singlePeer = (threads.size() > 1) + const auto singlePeer = (peers.size() > 1) ? (PeerData*)nullptr - : threads.front()->peer().get(); + : peers.front().get(); const auto recipientId = singlePeer ? singlePeer->id : PeerId(); const auto check = [=] { const auto required = details.stars; @@ -303,8 +303,8 @@ void ShowSendPaidConfirm( done); }; auto usersOnly = true; - for (const auto &thread : threads) { - if (!thread->peer()->isUser()) { + for (const auto &peer : peers) { + if (!peer->isUser()) { usersOnly = false; break; } @@ -342,7 +342,7 @@ void ShowSendPaidConfirm( : tr::lng_payment_confirm_chats)( tr::now, lt_count, - int(threads.size()), + int(peers.size()), Ui::Text::RichLangValue)).append(' ').append( tr::lng_payment_confirm_sure( tr::now, diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index 4106df38f..c5dc8795e 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -167,7 +167,7 @@ void ShowSendPaidConfirm( PaidConfirmStyles styles = {}); void ShowSendPaidConfirm( std::shared_ptr show, - const std::vector> &threads, + const std::vector> &peers, SendPaymentDetails details, Fn confirmed, PaidConfirmStyles styles = {}); diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index b7d216b71..c5fde09fd 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -436,7 +436,7 @@ void BottomInfo::layoutDateText() { auto marked = TextWithEntities{ full }; if (const auto count = _data.stars) { marked.append(Ui::Text::IconEmoji(&st::starIconEmoji)); - marked.append(QString::number(count)); + marked.append(Lang::FormatCountToShort(count).string); } _authorEditedDate.setMarkedText( st::msgDateTextStyle, diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index a56cba037..6d112e5d3 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -1809,11 +1809,15 @@ void WebViewInstance::botSendPreparedMessage( QPointer preview; QPointer choose; rpl::event_stream> recipient; + Fn send; + SendPaymentHelper sendPayment; bool sent = false; }; const auto state = std::make_shared(); auto recipient = state->recipient.events(); - const auto send = [=](std::vector> list) { + const auto send = [=]( + std::vector> list, + Api::SendOptions options) { if (state->sent) { return; } @@ -1852,7 +1856,7 @@ void WebViewInstance::botSendPreparedMessage( bot->session().api().sendInlineResult( bot, parsed.get(), - Api::SendAction(thread), + Api::SendAction(thread, options), std::nullopt, done); } @@ -1881,7 +1885,33 @@ void WebViewInstance::botSendPreparedMessage( state->choose = box.data(); panel->showBox(std::move(box)); }, [=](not_null thread) { - send({ thread }); + const auto weak = base::make_weak(thread); + state->send = [=](Api::SendOptions options) { + const auto strong = weak.get(); + if (!strong) { + state->send = nullptr; + return; + } + const auto withPaymentApproved = [=](int stars) { + if (const auto onstack = state->send) { + auto copy = options; + copy.starsApproved = stars; + onstack(copy); + } + }; + const auto checked = state->sendPayment.check( + uiShow(), + strong->peer(), + 1, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + state->send = nullptr; + send({ strong }, options); + }; + state->send({}); }); box->boxClosing() | rpl::start_with_next([=] { if (!state->sent) { diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 606a547f0..1e794a30f 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1960,7 +1960,9 @@ object_ptr PrepareChooseRecipientBox( rpl::producer titleOverride, FnMut &&successCallback, InlineBots::PeerTypes typesRestriction, - Fn>)> sendMany) { + Fn>, + Api::SendOptions)> sendMany) { const auto weak = std::make_shared>(); const auto selectable = (sendMany != nullptr); class Controller final : public ChooseRecipientBoxController { @@ -2073,19 +2075,82 @@ object_ptr PrepareChooseRecipientBox( std::move(filter), selectable); const auto raw = controller.get(); + + struct State { + Fn submit; + rpl::lifetime submitLifetime; + }; + const auto state = std::make_shared(); auto initBox = [=](not_null box) { raw->hasSelectedChanges( ) | rpl::start_with_next([=](bool shown) { box->clearButtons(); if (shown) { - box->addButton(tr::lng_send_button(), [=] { + const auto weak = Ui::MakeWeak(box); + state->submit = [=](Api::SendOptions options) { + state->submitLifetime.destroy(); + const auto show = box->peerListUiShow(); const auto peers = box->collectSelectedRows(); + const auto withPaymentApproved = crl::guard(weak, [=]( + int approved) { + auto copy = options; + copy.starsApproved = approved; + if (const auto onstack = state->submit) { + onstack(copy); + } + }); + + const auto alreadyApproved = options.starsApproved; + auto paid = std::vector>(); + auto waiting = base::flat_set>(); + auto totalStars = 0; + for (const auto &peer : peers) { + const auto details = ComputePaymentDetails(peer, 1); + if (!details) { + waiting.emplace(peer); + } else if (details->stars > 0) { + totalStars += details->stars; + paid.push_back(peer); + } + } + if (!waiting.empty()) { + session->changes().peerUpdates( + Data::PeerUpdate::Flag::FullInfo + ) | rpl::start_with_next([=](const Data::PeerUpdate &update) { + if (waiting.contains(update.peer)) { + withPaymentApproved(alreadyApproved); + } + }, state->submitLifetime); + + if (!session->credits().loaded()) { + session->credits().loadedValue( + ) | rpl::filter( + rpl::mappers::_1 + ) | rpl::take(1) | rpl::start_with_next([=] { + withPaymentApproved(alreadyApproved); + }, state->submitLifetime); + } + return; + } else if (totalStars > alreadyApproved) { + ShowSendPaidConfirm(show, paid, SendPaymentDetails{ + .messages = 1, + .stars = totalStars, + }, [=] { withPaymentApproved(totalStars); }); + return; + } + state->submit = nullptr; + sendMany(ranges::views::all( peers ) | ranges::views::transform([&]( not_null peer) -> Controller::Chosen { return peer->owner().history(peer); - }) | ranges::to_vector); + }) | ranges::to_vector, options); + }; + box->addButton(tr::lng_send_button(), [=] { + if (const auto onstack = state->submit) { + onstack({}); + } }); } box->addButton(tr::lng_cancel(), [=] { @@ -2257,6 +2322,8 @@ QPointer ShowForwardMessagesBox( not_null box; not_null controller; base::unique_qptr menu; + Fn submit; + rpl::lifetime submitLifetime; }; const auto applyFilter = [=](not_null box, FilterId id) { @@ -2401,8 +2468,62 @@ QPointer ShowForwardMessagesBox( session->data().message(msgIds.front())->history(), msgIds); - const auto submit = [=](Api::SendOptions options) { + const auto weak = Ui::MakeWeak(state->box); + state->submit = [=](Api::SendOptions options) { const auto peers = state->box->collectSelectedRows(); + const auto checkPaid = [=](int messagesCount) { + const auto withPaymentApproved = crl::guard(weak, [=]( + int approved) { + auto copy = options; + copy.starsApproved = approved; + if (const auto onstack = state->submit) { + onstack(copy); + } + }); + + const auto alreadyApproved = options.starsApproved; + auto paid = std::vector>(); + auto waiting = base::flat_set>(); + auto totalStars = 0; + for (const auto &peer : peers) { + const auto details = ComputePaymentDetails( + peer, + messagesCount); + if (!details) { + waiting.emplace(peer); + } else if (details->stars > 0) { + totalStars += details->stars; + paid.push_back(peer); + } + } + if (!waiting.empty()) { + session->changes().peerUpdates( + Data::PeerUpdate::Flag::FullInfo + ) | rpl::start_with_next([=](const Data::PeerUpdate &update) { + if (waiting.contains(update.peer)) { + withPaymentApproved(alreadyApproved); + } + }, state->submitLifetime); + + if (!session->credits().loaded()) { + session->credits().loadedValue( + ) | rpl::filter( + rpl::mappers::_1 + ) | rpl::take(1) | rpl::start_with_next([=] { + withPaymentApproved(alreadyApproved); + }, state->submitLifetime); + } + return false; + } else if (totalStars > alreadyApproved) { + ShowSendPaidConfirm(show, paid, SendPaymentDetails{ + .messages = messagesCount, + .stars = totalStars, + }, [=] { withPaymentApproved(totalStars); }); + return false; + } + state->submit = nullptr; + return true; + }; send( ranges::views::all( peers @@ -2410,11 +2531,11 @@ QPointer ShowForwardMessagesBox( not_null peer) -> Controller::Chosen { return peer->owner().history(peer); }) | ranges::to_vector, - [](int messagesCount) { return true; }, + checkPaid, comment->entity()->getTextWithAppliedMarkdown(), options, state->box->forwardOptionsData()); - if (successCallback) { + if (!state->submit && successCallback) { successCallback(); } }; @@ -2483,7 +2604,12 @@ QPointer ShowForwardMessagesBox( state->menu.get(), show, SendMenu::Details{ sendMenuType() }, - SendMenu::DefaultCallback(show, crl::guard(parent, submit))); + SendMenu::DefaultCallback(show, crl::guard(parent, [=]( + Api::SendOptions options) { + if (const auto onstack = state->submit) { + onstack(options); + } + }))); if (showForwardOptions || !state->menu->empty()) { state->menu->popup(QCursor::pos()); } @@ -2505,7 +2631,11 @@ QPointer ShowForwardMessagesBox( const auto field = comment->entity(); field->submits( - ) | rpl::start_with_next([=] { submit({}); }, field->lifetime()); + ) | rpl::start_with_next([=] { + if (const auto onstack = state->submit) { + onstack({}); + } + }, field->lifetime()); InitMessageFieldHandlers({ .session = session, .show = show, @@ -2529,9 +2659,12 @@ QPointer ShowForwardMessagesBox( ) | rpl::start_with_next([=](bool shown) { state->box->clearButtons(); if (shown) { - const auto send = state->box->addButton( - tr::lng_send_button(), - [=] { submit({}); }); + auto text = tr::lng_send_button(); + const auto send = state->box->addButton(std::move(text), [=] { + if (const auto onstack = state->submit) { + onstack({}); + } + }); send->setAcceptBoth(); send->clicks( ) | rpl::start_with_next([=](Qt::MouseButton button) { diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index 7e2314f59..97268366c 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -15,6 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; +namespace Api { +struct SendOptions; +} // namespace Api + namespace Ui { class RpWidget; class BoxContent; @@ -146,7 +150,9 @@ object_ptr PrepareChooseRecipientBox( rpl::producer titleOverride = nullptr, FnMut &&successCallback = nullptr, InlineBots::PeerTypes typesRestriction = 0, - Fn>)> sendMany = nullptr); + Fn>, + Api::SendOptions)> sendMany = nullptr); QPointer ShowChooseRecipientBox( not_null navigation, FnMut)> &&chosen, From 1684465e04c2232a26f46f5f9731bc4543782dbc Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 25 Feb 2025 18:59:16 +0400 Subject: [PATCH 061/103] Add sending paid stories replies. --- Telegram/Resources/langs/lang.strings | 6 +- .../history/history_item_helpers.cpp | 19 +- .../history/history_item_helpers.h | 8 +- .../SourceFiles/history/history_widget.cpp | 118 ++++------- Telegram/SourceFiles/history/history_widget.h | 21 +- .../history_view_compose_controls.cpp | 15 +- .../history/view/history_view_element.cpp | 3 + .../history/view/history_view_message.cpp | 12 +- .../view/history_view_replies_section.cpp | 138 +++++++++++-- .../view/history_view_replies_section.h | 14 +- .../media/stories/media_stories_reply.cpp | 192 +++++++++++++++--- .../media/stories/media_stories_reply.h | 16 +- .../ui/chat/attach/attach_prepare.cpp | 21 ++ .../ui/chat/attach/attach_prepare.h | 15 ++ 14 files changed, 442 insertions(+), 156 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 8f6a32ad8..294adcadb 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2167,14 +2167,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_set_chat_intro" = "{from} added the message below for all empty chats. How?"; "lng_action_payment_refunded" = "{peer} refunded {amount}"; "lng_action_paid_message_sent#one" = "You paid {count} Star to {action}"; -"lng_action_paid_message_sent#other" = "You paid {count} Star to {action}"; -"lng_action_paid_message_group#one" = "{from} paid {count} Star to {action}"; -"lng_action_paid_message_group#other" = "{from} paid {count} Star to {action}"; +"lng_action_paid_message_sent#other" = "You paid {count} Stars to {action}"; "lng_action_paid_message_one" = "send a message"; "lng_action_paid_message_some#one" = "send {count} message"; "lng_action_paid_message_some#other" = "send {count} messages"; "lng_action_paid_message_got#one" = "You received {count} Star from {name}"; "lng_action_paid_message_got#other" = "You received {count} Stars from {name}"; +"lng_you_paid_stars#one" = "You paid {count} Star."; +"lng_you_paid_stars#other" = "You paid {count} Stars."; "lng_similar_channels_title" = "Similar channels"; "lng_similar_channels_view_all" = "View all"; diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 49fcebcb2..5c601a700 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -382,13 +382,15 @@ bool SendPaymentHelper::check( not_null peer, int messagesCount, int starsApproved, - Fn resend) { + Fn resend, + PaidConfirmStyles styles) { return check( navigation->uiShow(), peer, messagesCount, starsApproved, - std::move(resend)); + std::move(resend), + styles); } bool SendPaymentHelper::check( @@ -396,8 +398,10 @@ bool SendPaymentHelper::check( not_null peer, int messagesCount, int starsApproved, - Fn resend) { - _lifetime.destroy(); + Fn resend, + PaidConfirmStyles styles) { + clear(); + const auto details = ComputePaymentDetails(peer, messagesCount); if (!details) { _resend = [=] { resend(starsApproved); }; @@ -426,12 +430,17 @@ bool SendPaymentHelper::check( } else if (const auto stars = details->stars; stars > starsApproved) { ShowSendPaidConfirm(show, peer, *details, [=] { resend(stars); - }); + }, styles); return false; } return true; } +void SendPaymentHelper::clear() { + _lifetime.destroy(); + _resend = nullptr; +} + void RequestDependentMessageItem( not_null item, PeerId peerId, diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index c5dc8795e..dfe2c737c 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -179,13 +179,17 @@ public: not_null peer, int messagesCount, int starsApproved, - Fn resend); + Fn resend, + PaidConfirmStyles styles = {}); [[nodiscard]] bool check( std::shared_ptr show, not_null peer, int messagesCount, int starsApproved, - Fn resend); + Fn resend, + PaidConfirmStyles styles = {}); + + void clear(); private: Fn _resend; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index e72412884..964c08888 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -225,16 +225,6 @@ const auto kPsaAboutPrefix = "cloud_lng_about_psa_"; } // namespace -struct HistoryWidget::SendingFiles { - std::vector groups; - Ui::SendFilesWay way; - TextWithTags caption; - Api::SendOptions options; - int totalCount = 0; - bool sendComment = false; - bool ctrlShiftEnter = false; -}; - HistoryWidget::HistoryWidget( QWidget *parent, not_null controller) @@ -898,17 +888,6 @@ HistoryWidget::HistoryWidget( } }, lifetime()); - if (!session().credits().loaded()) { - session().credits().loadedValue( - ) | rpl::filter( - rpl::mappers::_1 - ) | rpl::take(1) | rpl::start_with_next([=] { - if (const auto callback = base::take(_resendOnFullUpdated)) { - callback(); - } - }, lifetime()); - } - using Type = Data::DefaultNotify; rpl::merge( session().data().notifySettings().defaultUpdates(Type::User), @@ -2400,7 +2379,7 @@ void HistoryWidget::showHistory( setHistory(nullptr); _list = nullptr; _peer = nullptr; - _resendOnFullUpdated = nullptr; + _sendPayment.clear(); _topicsRequested.clear(); _canSendMessages = false; _canSendTexts = false; @@ -4412,7 +4391,7 @@ void HistoryWidget::send(Api::SendOptions options) { if (showSendMessageError( message.textWithTags, ignoreSlowmodeCountdown, - crl::guard(this, withPaymentApproved), + withPaymentApproved, options.starsApproved)) { return; } @@ -4735,6 +4714,19 @@ FullMsgId HistoryWidget::cornerButtonsCurrentId() { : FullMsgId(); } +bool HistoryWidget::checkSendPayment( + int messagesCount, + int starsApproved, + Fn withPaymentApproved) { + return _peer + && _sendPayment.check( + controller(), + _peer, + messagesCount, + starsApproved, + std::move(withPaymentApproved)); +} + void HistoryWidget::checkSuggestToGigagroup() { const auto group = _peer ? _peer->asMegagroup() : nullptr; if (!group || !group->owner().suggestToGigagroup(group)) { @@ -5890,7 +5882,7 @@ Data::ForumTopic *HistoryWidget::resolveReplyToTopic() { bool HistoryWidget::showSendMessageError( const TextWithTags &textWithTags, bool ignoreSlowmodeCountdown, - Fn resend, + Fn withPaymentApproved, int starsApproved) { if (!_canSendMessages) { return false; @@ -5908,25 +5900,12 @@ bool HistoryWidget::showSendMessageError( Data::ShowSendErrorToast(controller(), _peer, error); return true; } - return resend - && !checkSendPayment(request.messagesCount, starsApproved, resend); -} -bool HistoryWidget::checkSendPayment( - int messagesCount, - int starsApproved, - Fn resend) { - const auto details = ComputePaymentDetails(_peer, messagesCount); - if (!details) { - _resendOnFullUpdated = [=] { resend(starsApproved); }; - return false; - } else if (const auto stars = details->stars; stars > starsApproved) { - ShowSendPaidConfirm(controller(), _peer, *details, [=] { - resend(stars); - }); - return false; - } - return true; + return withPaymentApproved + && !checkSendPayment( + request.messagesCount, + starsApproved, + withPaymentApproved); } bool HistoryWidget::confirmSendingFiles(const QStringList &files) { @@ -6012,11 +5991,11 @@ bool HistoryWidget::confirmSendingFiles( } void HistoryWidget::sendingFilesConfirmed( - Ui::PreparedList &&list, - Ui::SendFilesWay way, - TextWithTags &&caption, - Api::SendOptions options, - bool ctrlShiftEnter) { + Ui::PreparedList &&list, + Ui::SendFilesWay way, + TextWithTags &&caption, + Api::SendOptions options, + bool ctrlShiftEnter) { Expects(list.filesToProcess.empty()); const auto compress = way.sendImagesAsPhotos(); @@ -6024,55 +6003,51 @@ void HistoryWidget::sendingFilesConfirmed( return; } - const auto filesCount = int(list.files.size()); auto groups = DivideByGroups( std::move(list), way, _peer->slowmodeApplied()); - const auto sendComment = !caption.text.isEmpty() - && (groups.size() != 1 || !groups.front().sentWithCaption()); - sendingFilesConfirmed(std::make_shared(SendingFiles{ - .groups = std::move(groups), - .way = way, - .caption = std::move(caption), - .options = options, - .totalCount = filesCount + (sendComment ? 1 : 0), - .sendComment = sendComment, - .ctrlShiftEnter = ctrlShiftEnter, - })); + auto bundle = PrepareFilesBundle( + std::move(groups), + way, + std::move(caption), + ctrlShiftEnter); + sendingFilesConfirmed(std::move(bundle), options); } void HistoryWidget::sendingFilesConfirmed( - std::shared_ptr args) { + std::shared_ptr bundle, + Api::SendOptions options) { const auto withPaymentApproved = [=](int approved) { - args->options.starsApproved = approved; - sendingFilesConfirmed(args); + auto copy = options; + copy.starsApproved = approved; + sendingFilesConfirmed(bundle, copy); }; const auto checked = checkSendPayment( - args->totalCount, - args->options.starsApproved, + bundle->totalCount, + options.starsApproved, withPaymentApproved); if (!checked) { return; } - const auto compress = args->way.sendImagesAsPhotos(); + const auto compress = bundle->way.sendImagesAsPhotos(); const auto type = compress ? SendMediaType::Photo : SendMediaType::File; - auto action = prepareSendAction(args->options); + auto action = prepareSendAction(options); action.clearDraft = false; - if (args->sendComment) { + if (bundle->sendComment) { auto message = Api::MessageToSend(action); - message.textWithTags = base::take(args->caption); + message.textWithTags = base::take(bundle->caption); session().api().sendMessage(std::move(message)); } - for (auto &group : args->groups) { + for (auto &group : bundle->groups) { const auto album = (group.type != Ui::AlbumType::None) ? std::make_shared() : nullptr; session().api().sendFiles( std::move(group.list), type, - base::take(args->caption), + base::take(bundle->caption), album, action); } @@ -8545,9 +8520,6 @@ void HistoryWidget::fullInfoUpdated() { updateControlsVisibility(); updateControlsGeometry(); } - if (const auto callback = base::take(_resendOnFullUpdated)) { - callback(); - } } void HistoryWidget::handlePeerUpdate() { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index f52dd3086..2b956c2c1 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/controls/history_view_compose_media_edit_manager.h" #include "history/view/history_view_corner_buttons.h" #include "history/history_drag_area.h" +#include "history/history_item_helpers.h" #include "history/history_view_highlight_manager.h" #include "history/history_view_top_toast.h" #include "history/history.h" @@ -69,6 +70,7 @@ class PinnedBar; class GroupCallBar; class RequestsBar; struct PreparedList; +struct PreparedBundle; class SendFilesWay; class SendAsButton; class SpoilerAnimation; @@ -320,7 +322,6 @@ private: using TabbedPanel = ChatHelpers::TabbedPanel; using TabbedSelector = ChatHelpers::TabbedSelector; using VoiceToSend = HistoryView::Controls::VoiceToSend; - struct SendingFiles; enum ScrollChangeType { ScrollChangeNone, @@ -359,6 +360,11 @@ private: bool cornerButtonsUnreadMayBeShown() override; bool cornerButtonsHas(HistoryView::CornerButtonType type) override; + [[nodiscard]] bool checkSendPayment( + int messagesCount, + int starsApproved, + Fn withPaymentApproved); + void checkSuggestToGigagroup(); void processReply(); void setReplyFieldsFromProcessing(); @@ -471,12 +477,8 @@ private: bool showSendMessageError( const TextWithTags &textWithTags, bool ignoreSlowmodeCountdown, - Fn resend = nullptr, + Fn withPaymentApproved = nullptr, int starsApproved = 0); - bool checkSendPayment( - int messagesCount, - int starsApproved, - Fn resend); void sendingFilesConfirmed( Ui::PreparedList &&list, @@ -484,7 +486,9 @@ private: TextWithTags &&caption, Api::SendOptions options, bool ctrlShiftEnter); - void sendingFilesConfirmed(std::shared_ptr args); + void sendingFilesConfirmed( + std::shared_ptr bundle, + Api::SendOptions options); void uploadFile(const QByteArray &fileContent, SendMediaType type); void itemRemoved(not_null item); @@ -769,7 +773,6 @@ private: std::unique_ptr _autocomplete; std::unique_ptr _emojiSuggestions; object_ptr _supportAutocomplete; - Fn _resendOnFullUpdated; UserData *_inlineBot = nullptr; QString _inlineBotUsername; @@ -874,6 +877,8 @@ private: int _topDelta = 0; + SendPaymentHelper _sendPayment; + rpl::event_stream<> _cancelRequests; }; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 1f88adeec..dc323ba7d 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1722,13 +1722,20 @@ void ComposeControls::updateFieldPlaceholder() { } _field->setPlaceholder([&] { + const auto peer = _history ? _history->peer.get() : nullptr; if (_fieldCustomPlaceholder) { return rpl::duplicate(_fieldCustomPlaceholder); } else if (isEditingMessage()) { return tr::lng_edit_message_text(); - } else if (!_history) { + } else if (!peer) { return tr::lng_message_ph(); - } else if (const auto channel = _history->peer->asChannel()) { + } else if (const auto stars = peer->starsPerMessageChecked()) { + return tr::lng_message_paid_ph( + lt_amount, + tr::lng_prize_credits_amount( + lt_count, + rpl::single(stars * 1.))); + } else if (const auto channel = peer->asChannel()) { if (channel->isBroadcast()) { return session().data().notifySettings().silentPosts(channel) ? tr::lng_broadcast_silent_ph() @@ -3120,6 +3127,7 @@ void ComposeControls::initWebpageProcess() { | Data::PeerUpdate::Flag::Notifications | Data::PeerUpdate::Flag::MessagesTTL | Data::PeerUpdate::Flag::FullInfo + | Data::PeerUpdate::Flag::StarsPerMessage ) | rpl::filter([peer = _history->peer](const Data::PeerUpdate &update) { return (update.peer.get() == peer); }) | rpl::map([](const Data::PeerUpdate &update) { @@ -3135,6 +3143,9 @@ void ComposeControls::initWebpageProcess() { if (flags & Data::PeerUpdate::Flag::MessagesTTL) { updateMessagesTTLShown(); } + if (flags & Data::PeerUpdate::Flag::StarsPerMessage) { + updateFieldPlaceholder(); + } if (flags & Data::PeerUpdate::Flag::FullInfo) { if (updateBotCommandShown()) { updateControlsVisibility(); diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 4dddabdfb..44e9e658f 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -384,6 +384,9 @@ QString DateTooltipText(not_null view) { if (item->isScheduled() && item->isSilent()) { dateText += '\n' + QChar(0xD83D) + QChar(0xDD15); } + if (const auto stars = item->out() ? item->starsPaid() : 0) { + dateText += '\n' + tr::lng_you_paid_stars(tr::now, lt_count, stars); + } return dateText; } diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 19d3ea733..b9a4b0a1e 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -476,22 +476,12 @@ void Message::initPaidInformation() { lt_action, action(), Ui::Text::WithEntities) - : history()->peer->isUser() - ? tr::lng_action_paid_message_got( + : tr::lng_action_paid_message_got( tr::now, lt_count, info.stars, lt_name, Ui::Text::Link(item->from()->shortName(), 1), - Ui::Text::WithEntities) - : tr::lng_action_paid_message_group( - tr::now, - lt_count, - info.stars, - lt_from, - Ui::Text::Link(item->from()->shortName(), 1), - lt_action, - action(), Ui::Text::WithEntities), }; if (!item->out()) { diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 3ffc1348e..88f2b44a5 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -733,8 +733,8 @@ void RepliesWidget::setupComposeControls() { }, lifetime()); _composeControls->sendVoiceRequests( - ) | rpl::start_with_next([=](ComposeControls::VoiceToSend &&data) { - sendVoice(std::move(data)); + ) | rpl::start_with_next([=](const ComposeControls::VoiceToSend &data) { + sendVoice(data); }, lifetime()); _composeControls->sendCommandRequests( @@ -1070,25 +1070,59 @@ void RepliesWidget::sendingFilesConfirmed( std::move(list), way, _history->peer->slowmodeApplied()); - const auto type = way.sendImagesAsPhotos() - ? SendMediaType::Photo - : SendMediaType::File; + auto bundle = PrepareFilesBundle( + std::move(groups), + way, + std::move(caption), + ctrlShiftEnter); + sendingFilesConfirmed(std::move(bundle), options); +} + +bool RepliesWidget::checkSendPayment( + int messagesCount, + int starsApproved, + Fn withPaymentApproved) { + return _sendPayment.check( + controller(), + _history->peer, + messagesCount, + starsApproved, + std::move(withPaymentApproved)); +} + +void RepliesWidget::sendingFilesConfirmed( + std::shared_ptr bundle, + Api::SendOptions options) { + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + sendingFilesConfirmed(bundle, copy); + }; + const auto checked = checkSendPayment( + bundle->totalCount, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + + const auto compress = bundle->way.sendImagesAsPhotos(); + const auto type = compress ? SendMediaType::Photo : SendMediaType::File; auto action = prepareSendAction(options); action.clearDraft = false; - if ((groups.size() != 1 || !groups.front().sentWithCaption()) - && !caption.text.isEmpty()) { + if (bundle->sendComment) { auto message = Api::MessageToSend(action); - message.textWithTags = base::take(caption); + message.textWithTags = base::take(bundle->caption); session().api().sendMessage(std::move(message)); } - for (auto &group : groups) { + for (auto &group : bundle->groups) { const auto album = (group.type != Ui::AlbumType::None) ? std::make_shared() : nullptr; session().api().sendFiles( std::move(group.list), type, - base::take(caption), + base::take(bundle->caption), album, action); } @@ -1227,7 +1261,20 @@ void RepliesWidget::send() { send({}); } -void RepliesWidget::sendVoice(ComposeControls::VoiceToSend &&data) { +void RepliesWidget::sendVoice(const ComposeControls::VoiceToSend &data) { + const auto withPaymentApproved = [=](int approved) { + auto copy = data; + copy.options.starsApproved = approved; + sendVoice(copy); + }; + const auto checked = checkSendPayment( + 1, + data.options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + auto action = prepareSendAction(data.options); session().api().sendVoiceMessage( data.bytes, @@ -1254,19 +1301,32 @@ void RepliesWidget::send(Api::SendOptions options) { message.textWithTags = _composeControls->getTextWithAppliedMarkdown(); message.webPage = _composeControls->webPageDraft(); - const auto error = GetErrorForSending( - _history->peer, - { - .topicRootId = _topic ? _topic->rootId() : MsgId(0), - .forward = &_composeControls->forwardItems(), - .text = &message.textWithTags, - .ignoreSlowmodeCountdown = (options.scheduled != 0), - }); + auto request = SendingErrorRequest{ + .topicRootId = _topic ? _topic->rootId() : MsgId(0), + .forward = &_composeControls->forwardItems(), + .text = &message.textWithTags, + .ignoreSlowmodeCountdown = (options.scheduled != 0), + }; + request.messagesCount = ComputeSendingMessagesCount(_history, request); + const auto error = GetErrorForSending(_history->peer, request); if (error) { Data::ShowSendErrorToast(controller(), _history->peer, error); return; } - + if (!options.scheduled) { + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + send(copy); + }; + const auto checked = checkSendPayment( + request.messagesCount, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + } session().api().sendMessage(std::move(message)); _composeControls->clear(); @@ -1420,6 +1480,18 @@ bool RepliesWidget::sendExistingDocument( || ShowSendPremiumError(controller(), document)) { return false; } + const auto withPaymentApproved = [=](int approved) { + auto copy = messageToSend; + copy.action.options.starsApproved = approved; + sendExistingDocument(document, std::move(copy), localId); + }; + const auto checked = checkSendPayment( + 1, + messageToSend.action.options.starsApproved, + withPaymentApproved); + if (!checked) { + return false; + } Api::SendExistingDocument( std::move(messageToSend), @@ -1448,6 +1520,19 @@ bool RepliesWidget::sendExistingPhoto( return false; } + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + sendExistingPhoto(photo, copy); + }; + const auto checked = checkSendPayment( + 1, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return false; + } + Api::SendExistingPhoto( Api::MessageToSend(prepareSendAction(options)), photo); @@ -1478,6 +1563,19 @@ void RepliesWidget::sendInlineResult( not_null bot, Api::SendOptions options, std::optional localMessageId) { + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + sendInlineResult(result, bot, copy, localMessageId); + }; + const auto checked = checkSendPayment( + 1, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + auto action = prepareSendAction(options); action.generateLocal = true; session().api().sendInlineResult( diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 7f3a6f4dc..d03f5c834 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/section_memento.h" #include "history/view/history_view_corner_buttons.h" #include "history/view/history_view_list_widget.h" +#include "history/history_item_helpers.h" #include "history/history_view_swipe_data.h" #include "data/data_messages.h" #include "base/timer.h" @@ -38,6 +39,7 @@ class PlainShadow; class FlatButton; class PinnedBar; struct PreparedList; +struct PreparedBundle; class SendFilesWay; } // namespace Ui @@ -207,6 +209,11 @@ private: void checkActivation() override; void doSetInnerFocus() override; + [[nodiscard]] bool checkSendPayment( + int messagesCount, + int starsApproved, + Fn withPaymentApproved); + void onScroll(); void updateInnerVisibleArea(); void updateControlsGeometry(); @@ -251,7 +258,7 @@ private: Api::SendOptions options) const; void send(); void send(Api::SendOptions options); - void sendVoice(Controls::VoiceToSend &&data); + void sendVoice(const Controls::VoiceToSend &data); void edit( not_null item, Api::SendOptions options, @@ -308,6 +315,9 @@ private: TextWithTags &&caption, Api::SendOptions options, bool ctrlShiftEnter); + void sendingFilesConfirmed( + std::shared_ptr bundle, + Api::SendOptions options); bool sendExistingDocument( not_null document, @@ -380,6 +390,8 @@ private: HistoryView::ChatPaintGestureHorizontalData _gestureHorizontal; + SendPaymentHelper _sendPayment; + int _lastScrollTop = 0; int _topicReopenBarHeight = 0; int _scrollTopDelta = 0; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index ad06525c3..21ae90050 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -15,11 +15,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "boxes/premium_limits_box.h" #include "boxes/send_files_box.h" +#include "boxes/share_box.h" // ShareBoxStyleOverrides #include "chat_helpers/compose/compose_show.h" #include "chat_helpers/tabbed_selector.h" #include "core/file_utilities.h" #include "core/mime_type.h" #include "data/stickers/data_custom_emoji.h" +#include "data/data_changes.h" #include "data/data_chat_participant_status.h" #include "data/data_document.h" #include "data/data_message_reaction_id.h" @@ -28,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "history/view/controls/compose_controls_common.h" #include "history/view/controls/history_view_compose_controls.h" +#include "history/view/history_view_schedule_box.h" // ScheduleBoxStyleArgs #include "history/history_item_helpers.h" #include "history/history.h" #include "inline_bots/inline_bot_result.h" @@ -36,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/stories/media_stories_controller.h" #include "media/stories/media_stories_stealth.h" #include "menu/menu_send.h" +#include "settings/settings_credits_graphics.h" // DarkCreditsEntryBoxStyle #include "storage/localimageloader.h" #include "storage/storage_account.h" #include "storage/storage_media_prepare.h" @@ -52,14 +56,19 @@ namespace { [[nodiscard]] rpl::producer PlaceholderText( const std::shared_ptr &show, - rpl::producer isComment) { + rpl::producer isComment, + rpl::producer starsPerMessage) { return rpl::combine( show->session().data().stories().stealthModeValue(), - std::move(isComment) - ) | rpl::map([](Data::StealthMode value, bool isComment) { - return std::tuple(value.enabledTill, isComment); + std::move(isComment), + std::move(starsPerMessage) + ) | rpl::map([]( + Data::StealthMode value, + bool isComment, + int starsPerMessage) { + return std::tuple(value.enabledTill, isComment, starsPerMessage); }) | rpl::distinct_until_changed( - ) | rpl::map([](TimeId till, bool isComment) { + ) | rpl::map([](TimeId till, bool isComment, int starsPerMessage) { return rpl::single( rpl::empty ) | rpl::then( @@ -71,7 +80,13 @@ namespace { }) | rpl::then( rpl::single(0) ) | rpl::map([=](TimeId left) { - return left + return starsPerMessage + ? tr::lng_message_paid_ph( + lt_amount, + tr::lng_prize_credits_amount( + lt_count, + rpl::single(starsPerMessage * 1.))) + : left ? tr::lng_stealth_mode_countdown( lt_left, rpl::single(TimeLeftText(left))) @@ -128,7 +143,8 @@ ReplyArea::ReplyArea(not_null controller) .stickerOrEmojiChosen = _controller->stickerOrEmojiChosen(), .customPlaceholder = PlaceholderText( _controller->uiShow(), - rpl::deferred([=] { return _isComment.value(); })), + rpl::deferred([=] { return _isComment.value(); }), + rpl::deferred([=] { return _starsForMessage.value(); })), .voiceCustomCancelText = tr::lng_record_cancel_stories(tr::now), .voiceLockFromBottom = true, .features = { @@ -199,7 +215,7 @@ bool ReplyArea::sendReaction(const Data::ReactionId &id) { } } return !message.textWithTags.empty() - && send(std::move(message), {}, true); + && send(std::move(message), true); } void ReplyArea::send(Api::SendOptions options) { @@ -209,29 +225,45 @@ void ReplyArea::send(Api::SendOptions options) { message.textWithTags = _controls->getTextWithAppliedMarkdown(); message.webPage = webPageDraft; - send(std::move(message), options); + send(std::move(message)); } bool ReplyArea::send( Api::MessageToSend message, - Api::SendOptions options, bool skipToast) { - if (!options.scheduled && showSlowmodeError()) { + if (!message.action.options.scheduled && showSlowmodeError()) { return false; } - const auto error = GetErrorForSending( - _data.peer, - { - .topicRootId = MsgId(0), - .text = &message.textWithTags, - .ignoreSlowmodeCountdown = (options.scheduled != 0), - }); + auto request = SendingErrorRequest{ + .topicRootId = MsgId(0), + .text = &message.textWithTags, + .ignoreSlowmodeCountdown = (message.action.options.scheduled != 0), + }; + request.messagesCount = ComputeSendingMessagesCount( + message.action.history, + request); + const auto error = GetErrorForSending(_data.peer, request); if (error) { Data::ShowSendErrorToast(_controller->uiShow(), _data.peer, error); return false; } + if (!message.action.options.scheduled) { + const auto withPaymentApproved = [=](int approved) { + auto copy = message; + copy.action.options.starsApproved = approved; + send(copy); + }; + const auto checked = checkSendPayment( + request.messagesCount, + message.action.options.starsApproved, + withPaymentApproved); + if (!checked) { + return false; + } + } + session().api().sendMessage(std::move(message)); finishSending(skipToast); @@ -239,7 +271,40 @@ bool ReplyArea::send( return true; } -void ReplyArea::sendVoice(VoiceToSend &&data) { +bool ReplyArea::checkSendPayment( + int messagesCount, + int starsApproved, + Fn withPaymentApproved) { + const auto st1 = ::Settings::DarkCreditsEntryBoxStyle(); + const auto st2 = st1.shareBox.get(); + const auto st3 = st2 ? st2->scheduleBox.get() : nullptr; + return _data.peer + && _sendPayment.check( + _controller->uiShow(), + _data.peer, + messagesCount, + starsApproved, + std::move(withPaymentApproved), + { + .label = st3 ? st3->chooseDateTimeArgs.labelStyle : nullptr, + .checkbox = st2 ? st2->checkbox : nullptr, + }); +} + +void ReplyArea::sendVoice(const VoiceToSend &data) { + const auto withPaymentApproved = [=](int approved) { + auto copy = data; + copy.options.starsApproved = approved; + sendVoice(copy); + }; + const auto checked = checkSendPayment( + 1, + data.options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + auto action = prepareSendAction(data.options); session().api().sendVoiceMessage( data.bytes, @@ -269,6 +334,18 @@ bool ReplyArea::sendExistingDocument( || Window::ShowSendPremiumError(show, document)) { return false; } + const auto withPaymentApproved = [=](int approved) { + auto copy = messageToSend; + copy.action.options.starsApproved = approved; + sendExistingDocument(document, std::move(copy), localId); + }; + const auto checked = checkSendPayment( + 1, + messageToSend.action.options.starsApproved, + withPaymentApproved); + if (!checked) { + return false; + } Api::SendExistingDocument(std::move(messageToSend), document, localId); @@ -296,6 +373,18 @@ bool ReplyArea::sendExistingPhoto( } else if (showSlowmodeError()) { return false; } + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + sendExistingPhoto(photo, copy); + }; + const auto checked = checkSendPayment( + 1, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return false; + } Api::SendExistingPhoto( Api::MessageToSend(prepareSendAction(options)), @@ -322,6 +411,19 @@ void ReplyArea::sendInlineResult( not_null bot, Api::SendOptions options, std::optional localMessageId) { + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + sendInlineResult(result, bot, copy, localMessageId); + }; + const auto checked = checkSendPayment( + 1, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + auto action = prepareSendAction(options); action.generateLocal = true; session().api().sendInlineResult( @@ -564,25 +666,47 @@ void ReplyArea::sendingFilesConfirmed( std::move(list), way, _data.peer->slowmodeApplied()); - const auto type = way.sendImagesAsPhotos() - ? SendMediaType::Photo - : SendMediaType::File; + auto bundle = PrepareFilesBundle( + std::move(groups), + way, + std::move(caption), + ctrlShiftEnter); + sendingFilesConfirmed(std::move(bundle), options); +} + +void ReplyArea::sendingFilesConfirmed( + std::shared_ptr bundle, + Api::SendOptions options) { + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + sendingFilesConfirmed(bundle, copy); + }; + const auto checked = checkSendPayment( + bundle->totalCount, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + + const auto compress = bundle->way.sendImagesAsPhotos(); + const auto type = compress ? SendMediaType::Photo : SendMediaType::File; auto action = prepareSendAction(options); action.clearDraft = false; - if ((groups.size() != 1 || !groups.front().sentWithCaption()) - && !caption.text.isEmpty()) { + if (bundle->sendComment) { auto message = Api::MessageToSend(action); - message.textWithTags = base::take(caption); + message.textWithTags = base::take(bundle->caption); session().api().sendMessage(std::move(message)); } - for (auto &group : groups) { + for (auto &group : bundle->groups) { const auto album = (group.type != Ui::AlbumType::None) ? std::make_shared() : nullptr; session().api().sendFiles( std::move(group.list), type, - base::take(caption), + base::take(bundle->caption), album, action); } @@ -618,8 +742,8 @@ void ReplyArea::initActions() { }, _lifetime); _controls->sendVoiceRequests( - ) | rpl::start_with_next([=](VoiceToSend &&data) { - sendVoice(std::move(data)); + ) | rpl::start_with_next([=](const VoiceToSend &data) { + sendVoice(data); }, _lifetime); _controls->attachRequests( @@ -697,6 +821,16 @@ void ReplyArea::show( _controls->clear(); } return; + } else if (const auto peer = _data.peer) { + using Flag = Data::PeerUpdate::Flag; + _starsForMessage = peer->session().changes().peerFlagsValue( + peer, + Flag::StarsPerMessage | Flag::FullInfo + ) | rpl::map([=] { + return peer->starsPerMessageChecked(); + }); + } else { + _starsForMessage = 0; } invalidate_weak_ptrs(&_shownPeerGuard); const auto peer = data.peer; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.h b/Telegram/SourceFiles/media/stories/media_stories_reply.h index d3cdec47d..bb7fe15f0 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "base/weak_ptr.h" +#include "history/history_item_helpers.h" class History; enum class SendMediaType; @@ -44,6 +45,7 @@ struct Details; namespace Ui { struct PreparedList; +struct PreparedBundle; class SendFilesWay; class RpWidget; } // namespace Ui @@ -90,9 +92,13 @@ private: bool send( Api::MessageToSend message, - Api::SendOptions options, bool skipToast = false); + [[nodiscard]] bool checkSendPayment( + int messagesCount, + int starsApproved, + Fn withPaymentApproved); + void uploadFile(const QByteArray &fileContent, SendMediaType type); bool confirmSendingFiles( QImage &&image, @@ -116,6 +122,9 @@ private: TextWithTags &&caption, Api::SendOptions options, bool ctrlShiftEnter); + void sendingFilesConfirmed( + std::shared_ptr bundle, + Api::SendOptions options); void finishSending(bool skipToast = false); bool sendExistingDocument( @@ -141,7 +150,7 @@ private: [[nodiscard]] Api::SendAction prepareSendAction( Api::SendOptions options) const; void send(Api::SendOptions options); - void sendVoice(VoiceToSend &&data); + void sendVoice(const VoiceToSend &data); void chooseAttach(std::optional overrideSendImagesAsPhotos); [[nodiscard]] Fn sendMenuDetails() const; @@ -151,6 +160,7 @@ private: const not_null _controller; rpl::variable _isComment; + rpl::variable _starsForMessage; const std::unique_ptr _controls; std::unique_ptr _cant; @@ -160,6 +170,8 @@ private: bool _chooseAttachRequest = false; rpl::variable _choosingAttach; + SendPaymentHelper _sendPayment; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp index 3c5e0e707..fb968607b 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp @@ -266,6 +266,27 @@ bool PreparedList::hasSpoilerMenu(bool compress) const { return allAreVideo || (allAreMedia && compress); } +std::shared_ptr PrepareFilesBundle( + std::vector groups, + SendFilesWay way, + TextWithTags caption, + bool ctrlShiftEnter) { + auto totalCount = 0; + for (const auto &group : groups) { + totalCount += group.list.files.size(); + } + const auto sendComment = !caption.text.isEmpty() + && (groups.size() != 1 || !groups.front().sentWithCaption()); + return std::make_shared(PreparedBundle{ + .groups = std::move(groups), + .way = way, + .caption = std::move(caption), + .totalCount = totalCount + (sendComment ? 1 : 0), + .sendComment = sendComment, + .ctrlShiftEnter = ctrlShiftEnter, + }); +} + int MaxAlbumItems() { return kMaxAlbumCount; } diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h index b244f321b..311903a00 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "editor/photo_editor_common.h" +#include "ui/chat/attach/attach_send_files_way.h" #include "ui/rect_part.h" #include @@ -153,6 +154,20 @@ struct PreparedGroup { SendFilesWay way, bool slowmode); +struct PreparedBundle { + std::vector groups; + SendFilesWay way; + TextWithTags caption; + int totalCount = 0; + bool sendComment = false; + bool ctrlShiftEnter = false; +}; +[[nodiscard]] std::shared_ptr PrepareFilesBundle( + std::vector groups, + SendFilesWay way, + TextWithTags caption, + bool ctrlShiftEnter); + [[nodiscard]] int MaxAlbumItems(); [[nodiscard]] bool ValidateThumbDimensions(int width, int height); From c6fd8bcb99883f733bcf76e1436979c17a5bf1d0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 26 Feb 2025 17:24:07 +0400 Subject: [PATCH 062/103] Edit paid messages exceptions. --- Telegram/SourceFiles/api/api_user_privacy.cpp | 3 + Telegram/SourceFiles/api/api_user_privacy.h | 1 + Telegram/SourceFiles/apiwrap.cpp | 11 ++- .../SourceFiles/boxes/edit_privacy_box.cpp | 72 ++++++++++++++-- Telegram/SourceFiles/data/data_channel.cpp | 12 +-- Telegram/SourceFiles/data/data_peer.cpp | 12 +++ Telegram/SourceFiles/data/data_peer.h | 4 +- Telegram/SourceFiles/data/data_user.cpp | 5 +- .../history/history_item_helpers.cpp | 16 +++- .../SourceFiles/storage/storage_account.cpp | 84 ++++++++++++++----- .../SourceFiles/storage/storage_account.h | 12 ++- 11 files changed, 184 insertions(+), 48 deletions(-) diff --git a/Telegram/SourceFiles/api/api_user_privacy.cpp b/Telegram/SourceFiles/api/api_user_privacy.cpp index 78f863bed..e07858dda 100644 --- a/Telegram/SourceFiles/api/api_user_privacy.cpp +++ b/Telegram/SourceFiles/api/api_user_privacy.cpp @@ -210,6 +210,7 @@ MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) { case Key::About: return MTP_inputPrivacyKeyAbout(); case Key::Birthday: return MTP_inputPrivacyKeyBirthday(); case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave(); + case Key::NoPaidMessages: return MTP_inputPrivacyKeyNoPaidMessages(); } Unexpected("Key in Api::UserPrivacy::KetToTL."); } @@ -241,6 +242,8 @@ std::optional TLToKey(mtpTypeId type) { case mtpc_inputPrivacyKeyBirthday: return Key::Birthday; case mtpc_privacyKeyStarGiftsAutoSave: case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave; + case mtpc_privacyKeyNoPaidMessages: + case mtpc_inputPrivacyKeyNoPaidMessages: return Key::NoPaidMessages; } return std::nullopt; } diff --git a/Telegram/SourceFiles/api/api_user_privacy.h b/Telegram/SourceFiles/api/api_user_privacy.h index a1f66189f..676e9be11 100644 --- a/Telegram/SourceFiles/api/api_user_privacy.h +++ b/Telegram/SourceFiles/api/api_user_privacy.h @@ -32,6 +32,7 @@ public: About, Birthday, GiftsAutoSave, + NoPaidMessages, }; enum class Option { Everyone, diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 1d6cb2ede..c5f407394 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -523,6 +523,7 @@ void ApiWrap::sendMessageFail( uint64 randomId, FullMsgId itemId) { const auto show = ShowForPeer(peer); + const auto paidStarsPrefix = u"ALLOW_PAYMENT_REQUIRED_"_q; if (show && error == u"PEER_FLOOD"_q) { show->showBox( Ui::MakeInformBox( @@ -577,11 +578,19 @@ void ApiWrap::sendMessageFail( if (show) { show->showToast(tr::lng_error_schedule_limit(tr::now)); } - } else if (error.startsWith(u"ALLOW_PAYMENT_REQUIRED_"_q)) { + } else if (error.startsWith(paidStarsPrefix)) { if (show) { show->showToast( u"Payment requirements changed. Please, try again."_q); } + if (const auto stars = error.mid(paidStarsPrefix.size()).toInt()) { + if (const auto user = peer->asUser()) { + user->setStarsPerMessage(stars); + } else if (const auto channel = peer->asChannel()) { + channel->setStarsPerMessage(stars); + } + } + peer->updateFull(); } if (const auto item = _session->data().message(itemId)) { Assert(randomId != 0); diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp index d905a5d9a..eba2b0e3a 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "boxes/peer_list_controllers.h" #include "settings/settings_premium.h" +#include "settings/settings_privacy_controllers.h" #include "settings/settings_privacy_security.h" #include "calls/calls_instance.h" #include "lang/lang_keys.h" @@ -561,6 +562,39 @@ auto PrivacyExceptionsBoxController::createRow(not_null history) return result; } +void EditNoPaidMessagesExceptions( + not_null window, + const Api::UserPrivacy::Rule &value) { + auto controller = std::make_unique( + &window->session(), + tr::lng_messages_privacy_remove_fee(), + value.always, + std::optional()); + auto initBox = [=, controller = controller.get()]( + not_null box) { + box->addButton(tr::lng_settings_save(), [=] { + auto copy = value; + auto &setTo = copy.always; + setTo.peers = box->collectSelectedRows(); + setTo.premiums = false; + setTo.miniapps = false; + auto &removeFrom = copy.never; + for (const auto peer : setTo.peers) { + removeFrom.peers.erase( + ranges::remove(removeFrom.peers, peer), + end(removeFrom.peers)); + } + window->session().api().userPrivacy().save( + Api::UserPrivacy::Key::NoPaidMessages, + copy); + box->closeBox(); + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + }; + window->show( + Box(std::move(controller), std::move(initBox))); +} + } // namespace bool EditPrivacyController::hasOption(Option option) const { @@ -923,11 +957,12 @@ void EditMessagesPrivacyBox( constexpr auto kOptionPremium = 1; constexpr auto kOptionCharge = 2; + const auto session = &controller->session(); const auto allowed = [=] { - return controller->session().premium() - || controller->session().appConfig().newRequirePremiumFree(); + return session->premium() + || session->appConfig().newRequirePremiumFree(); }; - const auto privacy = &controller->session().api().globalPrivacy(); + const auto privacy = &session->api().globalPrivacy(); const auto inner = box->verticalLayout(); inner->add(object_ptr(box)); @@ -995,18 +1030,45 @@ void EditMessagesPrivacyBox( state->stars = SetupChargeSlider( chargeInner, - controller->session().user(), + session->user(), savedValue); Ui::AddSkip(chargeInner); Ui::AddSubsectionTitle( chargeInner, tr::lng_messages_privacy_exceptions()); + + const auto key = Api::UserPrivacy::Key::NoPaidMessages; + session->api().userPrivacy().reload(key); + auto label = session->api().userPrivacy().value( + key + ) | rpl::map([=](const Api::UserPrivacy::Rule &value) { + using namespace Settings; + const auto always = ExceptionUsersCount(value.always.peers); + return always + ? tr::lng_edit_privacy_exceptions_count( + tr::now, + lt_count, + always) + : QString(); + }); + const auto exceptions = Settings::AddButtonWithLabel( chargeInner, tr::lng_messages_privacy_remove_fee(), - rpl::single(u""_q), + std::move(label), st::settingsButtonNoIcon); + + const auto shower = exceptions->lifetime().make_state(); + exceptions->setClickedCallback([=] { + *shower = session->api().userPrivacy().value( + key + ) | rpl::take( + 1 + ) | rpl::start_with_next([=](const Api::UserPrivacy::Rule &value) { + EditNoPaidMessagesExceptions(controller, value); + }); + }); Ui::AddSkip(chargeInner); Ui::AddDividerText(chargeInner, tr::lng_messages_privacy_remove_about()); diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 410abacf9..f02f31d3d 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -869,15 +869,11 @@ int ChannelData::starsPerMessage() const { } void ChannelData::setStarsPerMessage(int stars) { - if (!mgInfo || starsPerMessage() == stars) { - return; - } - const auto removed = mgInfo->_starsPerMessage && !stars; - mgInfo->_starsPerMessage = stars; - session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); - if (removed) { - session().local().clearPeerTrusted(id); + if (mgInfo && starsPerMessage() != stars) { + mgInfo->_starsPerMessage = stars; + session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); } + checkTrustedPayForMessage(); } int ChannelData::peerGiftsCount() const { diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 45849d439..ad9449593 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -50,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" #include "history/history_item.h" #include "storage/file_download.h" +#include "storage/storage_account.h" #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" @@ -341,6 +342,17 @@ void PeerData::invalidateEmptyUserpic() { _userpicEmpty = nullptr; } +void PeerData::checkTrustedPayForMessage() { + if (!_checkedTrustedPayForMessage + && !starsPerMessage() + && session().local().peerTrustedPayForMessageRead()) { + _checkedTrustedPayForMessage = 1; + if (session().local().hasPeerTrustedPayForMessageEntry(id)) { + session().local().clearPeerTrustedPayForMessage(id); + } + } +} + ClickHandlerPtr PeerData::createOpenLink() { return std::make_shared(this); } diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 8d1b09289..dcec75edf 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -504,6 +504,7 @@ protected: void updateUserpic(PhotoId photoId, MTP::DcId dcId, bool hasVideo); void clearUserpic(); void invalidateEmptyUserpic(); + void checkTrustedPayForMessage(); private: void fillNames(); @@ -538,9 +539,10 @@ private: crl::time _lastFullUpdate = 0; QString _name; - uint32 _nameVersion : 30 = 1; + uint32 _nameVersion : 29 = 1; uint32 _sensitiveContent : 1 = 0; uint32 _wallPaperOverriden : 1 = 0; + uint32 _checkedTrustedPayForMessage : 1 = 0; TimeId _ttlPeriod = 0; diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 903ea87e5..8c334421e 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -543,13 +543,10 @@ int UserData::starsPerMessage() const { void UserData::setStarsPerMessage(int stars) { if (_starsPerMessage != stars) { - const auto removed = _starsPerMessage && !stars; _starsPerMessage = stars; session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage); - if (removed) { - session().local().clearPeerTrusted(id); - } } + checkTrustedPayForMessage(); } bool UserData::canAddContact() const { diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 5c601a700..1909f4943 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -284,7 +284,7 @@ void ShowSendPaidConfirm( const auto singlePeer = (peers.size() > 1) ? (PeerData*)nullptr : peers.front().get(); - const auto recipientId = singlePeer ? singlePeer->id : PeerId(); + const auto singlePeerId = singlePeer ? singlePeer->id : PeerId(); const auto check = [=] { const auto required = details.stars; if (!required) { @@ -299,7 +299,7 @@ void ShowSendPaidConfirm( Settings::MaybeRequestBalanceIncrease( show, required, - Settings::SmallBalanceForMessage{ .recipientId = recipientId }, + Settings::SmallBalanceForMessage{ .recipientId = singlePeerId }, done); }; auto usersOnly = true; @@ -309,9 +309,15 @@ void ShowSendPaidConfirm( break; } } + const auto singlePeerStars = singlePeer + ? singlePeer->starsPerMessageChecked() + : 0; if (singlePeer) { const auto session = &singlePeer->session(); - if (session->local().isPeerTrustedPayForMessage(recipientId)) { + const auto trusted = session->local().isPeerTrustedPayForMessage( + singlePeerId, + singlePeerStars); + if (trusted) { check(); return; } @@ -323,7 +329,9 @@ void ShowSendPaidConfirm( const auto proceed = [=](Fn close) { if (singlePeer && (*trust)->checked()) { const auto session = &singlePeer->session(); - session->local().markPeerTrustedPayForMessage(recipientId); + session->local().markPeerTrustedPayForMessage( + singlePeerId, + singlePeerStars); } check(); close(); diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index 1472ebf5d..ea673e503 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -3148,7 +3148,7 @@ void Account::readSelf( } void Account::writeTrustedPeers() { - if (_trustedPeers.empty()) { + if (_trustedPeers.empty() && _trustedPayPerMessage.empty()) { if (_trustedPeersKey) { ClearKey(_trustedPeersKey, _basePath); _trustedPeersKey = 0; @@ -3160,7 +3160,10 @@ void Account::writeTrustedPeers() { _trustedPeersKey = GenerateKey(_basePath); writeMapQueued(); } - quint32 size = sizeof(qint32) + _trustedPeers.size() * sizeof(quint64); + quint32 size = sizeof(qint32) + + _trustedPeers.size() * sizeof(quint64) + + sizeof(qint32) + + _trustedPayPerMessage.size() * (sizeof(quint64) + sizeof(qint32)); EncryptedDescriptor data(size); data.stream << qint32(_trustedPeers.size()); for (const auto &[peerId, mask] : _trustedPeers) { @@ -3170,6 +3173,10 @@ void Account::writeTrustedPeers() { value |= (quint64(mask) << 56); data.stream << value; } + data.stream << qint32(_trustedPayPerMessage.size()); + for (const auto &[peerId, stars] : _trustedPayPerMessage) { + data.stream << SerializePeerId(peerId) << qint32(stars); + } FileWriteDescriptor file(_trustedPeersKey, _basePath); file.writeEncrypted(data, _localKey); @@ -3192,9 +3199,9 @@ void Account::readTrustedPeers() { return; } - qint32 size = 0; - trusted.stream >> size; - for (int i = 0; i < size; ++i) { + qint32 trustedCount = 0; + trusted.stream >> trustedCount; + for (int i = 0; i < trustedCount; ++i) { auto value = quint64(); trusted.stream >> value; const auto mask = base::flags::from_raw( @@ -3203,6 +3210,28 @@ void Account::readTrustedPeers() { const auto peerId = DeserializePeerId(peerIdSerialized); _trustedPeers.emplace(peerId, mask); } + if (trusted.stream.atEnd()) { + return; + } + qint32 payPerMessageCount = 0; + trusted.stream >> payPerMessageCount; + const auto owner = _owner->sessionExists() + ? &_owner->session().data() + : nullptr; + for (int i = 0; i < payPerMessageCount; ++i) { + auto value = quint64(); + auto stars = qint32(); + trusted.stream >> value >> stars; + const auto peerId = DeserializePeerId(value); + const auto peer = owner ? owner->peerLoaded(peerId) : nullptr; + const auto now = peer ? peer->starsPerMessage() : stars; + if (now > 0 && now <= stars) { + _trustedPayPerMessage.emplace(peerId, stars); + } + } + if (_trustedPayPerMessage.size() != payPerMessageCount) { + writeTrustedPeers(); + } } void Account::markPeerTrustedOpenGame(PeerId peerId) { @@ -3269,32 +3298,45 @@ bool Account::isPeerTrustedOpenWebView(PeerId peerId) { && ((i->second & PeerTrustFlag::OpenWebView) != 0); } -void Account::markPeerTrustedPayForMessage(PeerId peerId) { - if (isPeerTrustedPayForMessage(peerId)) { +void Account::markPeerTrustedPayForMessage( + PeerId peerId, + int starsPerMessage) { + if (isPeerTrustedPayForMessage(peerId, starsPerMessage)) { return; } - const auto i = _trustedPeers.find(peerId); - if (i == end(_trustedPeers)) { - _trustedPeers.emplace( - peerId, - PeerTrustFlag::NoOpenGame | PeerTrustFlag::PayForMessage); + const auto i = _trustedPayPerMessage.find(peerId); + if (i == end(_trustedPayPerMessage)) { + _trustedPayPerMessage.emplace(peerId, starsPerMessage); } else { - i->second |= PeerTrustFlag::PayForMessage; + i->second = starsPerMessage; } writeTrustedPeers(); } -bool Account::isPeerTrustedPayForMessage(PeerId peerId) { +bool Account::isPeerTrustedPayForMessage( + PeerId peerId, + int starsPerMessage) { + if (starsPerMessage <= 0) { + return true; + } readTrustedPeers(); - const auto i = _trustedPeers.find(peerId); - return (i != end(_trustedPeers)) - && ((i->second & PeerTrustFlag::PayForMessage) != 0); + const auto i = _trustedPayPerMessage.find(peerId); + return (i != end(_trustedPayPerMessage)) + && (i->second >= starsPerMessage); } -void Account::clearPeerTrusted(PeerId peerId) { - const auto i = _trustedPeers.find(peerId); - if (i != end(_trustedPeers)) { - _trustedPeers.erase(i); +bool Account::peerTrustedPayForMessageRead() const { + return _trustedPeersRead; +} + +bool Account::hasPeerTrustedPayForMessageEntry(PeerId peerId) const { + return _trustedPayPerMessage.contains(peerId); +} + +void Account::clearPeerTrustedPayForMessage(PeerId peerId) { + const auto i = _trustedPayPerMessage.find(peerId); + if (i != end(_trustedPayPerMessage)) { + _trustedPayPerMessage.erase(i); writeTrustedPeers(); } } diff --git a/Telegram/SourceFiles/storage/storage_account.h b/Telegram/SourceFiles/storage/storage_account.h index c9e977813..86bdabde6 100644 --- a/Telegram/SourceFiles/storage/storage_account.h +++ b/Telegram/SourceFiles/storage/storage_account.h @@ -173,9 +173,13 @@ public: [[nodiscard]] bool isPeerTrustedPayment(PeerId peerId); void markPeerTrustedOpenWebView(PeerId peerId); [[nodiscard]] bool isPeerTrustedOpenWebView(PeerId peerId); - void markPeerTrustedPayForMessage(PeerId peerId); - [[nodiscard]] bool isPeerTrustedPayForMessage(PeerId peerId); - void clearPeerTrusted(PeerId peerId); + void markPeerTrustedPayForMessage(PeerId peerId, int starsPerMessage); + [[nodiscard]] bool isPeerTrustedPayForMessage( + PeerId peerId, + int starsPerMessage); + [[nodiscard]] bool peerTrustedPayForMessageRead() const; + [[nodiscard]] bool hasPeerTrustedPayForMessageEntry(PeerId peerId) const; + void clearPeerTrustedPayForMessage(PeerId peerId); void enforceModernStorageIdBots(); [[nodiscard]] Webview::StorageId resolveStorageIdBots(); @@ -210,7 +214,6 @@ private: NoOpenGame = (1 << 0), Payment = (1 << 1), OpenWebView = (1 << 2), - PayForMessage = (1 << 3), }; friend inline constexpr bool is_flag_type(PeerTrustFlag) { return true; }; @@ -329,6 +332,7 @@ private: qint32 _cacheBigFileTotalTimeLimit = 0; base::flat_map> _trustedPeers; + base::flat_map _trustedPayPerMessage; bool _trustedPeersRead = false; bool _readingUserSettings = false; bool _recentHashtagsAndBotsWereRead = false; From 17cf354c5806e8b6c0ade74a25c29713207be7b6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 27 Feb 2025 17:34:20 +0400 Subject: [PATCH 063/103] Support custom send button for paid. --- .../chat_helpers/chat_helpers.style | 7 + .../SourceFiles/history/history_widget.cpp | 25 ++- .../history_view_compose_controls.cpp | 14 +- .../history_view_voice_record_bar.cpp | 20 +- .../SourceFiles/ui/controls/send_button.cpp | 175 ++++++++++++++---- .../SourceFiles/ui/controls/send_button.h | 34 +++- 6 files changed, 215 insertions(+), 60 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index a380b6d5f..d0d59bb5a 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -149,6 +149,7 @@ EmojiButton { SendButton { inner: IconButton; + stars: RoundButton; record: icon; recordOver: icon; round: icon; @@ -1293,6 +1294,12 @@ historySend: SendButton { icon: historySendIcon; iconOver: historySendIconOver; } + stars: RoundButton(defaultActiveButton) { + height: 28px; + padding: margins(0px, 0px, 6px, 0px); + textTop: 5px; + width: -8px; + } record: historyRecordVoice; recordOver: historyRecordVoiceOver; round: historyRecordRound; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 964c08888..c862ccc5e 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -491,6 +491,11 @@ HistoryWidget::HistoryWidget( moveFieldControls(); }, lifetime()); + _send->widthValue() | rpl::skip(1) | rpl::start_with_next([=] { + updateFieldSize(); + moveFieldControls(); + }, _send->lifetime()); + _keyboard->sendCommandRequests( ) | rpl::start_with_next([=](Bot::SendCommandRequest r) { sendBotCommand(r); @@ -810,6 +815,7 @@ HistoryWidget::HistoryWidget( }) | rpl::start_with_next([=](Data::PeerUpdate::Flags flags) { if (flags & PeerUpdateFlag::Rights) { updateFieldPlaceholder(); + updateSendButtonType(); _preview->checkNow(false); const auto was = (_sendAs != nullptr); @@ -839,6 +845,7 @@ HistoryWidget::HistoryWidget( } if (flags & PeerUpdateFlag::StarsPerMessage) { updateFieldPlaceholder(); + updateSendButtonType(); } if (flags & PeerUpdateFlag::BotStartToken) { updateControlsVisibility(); @@ -4138,7 +4145,7 @@ void HistoryWidget::checkReplyReturns() { } void HistoryWidget::cancelInlineBot() { - auto &textWithTags = _field->getTextWithTags(); + const auto &textWithTags = _field->getTextWithTags(); if (textWithTags.text.size() > _inlineBotUsername.size() + 2) { setFieldText( { '@' + _inlineBotUsername + ' ', TextWithTags::Tags() }, @@ -5134,19 +5141,27 @@ void HistoryWidget::updateSendButtonType() { using Type = Ui::SendButton::Type; const auto type = computeSendButtonType(); - _send->setType(type); - // This logic is duplicated in RepliesWidget. const auto disabledBySlowmode = _peer && _peer->slowmodeApplied() && (_history->latestSendingMessage() != nullptr); - const auto delay = [&] { return (type != Type::Cancel && type != Type::Save && _peer) ? _peer->slowmodeSecondsLeft() : 0; }(); - _send->setSlowmodeDelay(delay); + const auto perMessage = _peer ? _peer->starsPerMessageChecked() : 0; + const auto stars = perMessage + ? perMessage * ComputeSendingMessagesCount(_history, { + .forward = &_forwardPanel->items(), + .text = &_field->getTextWithTags(), + }) + : 0; + _send->setState({ + .type = (delay > 0) ? Type::Slowmode : type, + .slowmodeDelay = delay, + .starsToSend = stars, + }); _send->setDisabled(disabledBySlowmode && (type == Type::Send || type == Type::Record diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index dc323ba7d..1fee75916 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -2164,6 +2164,10 @@ void ComposeControls::initSendButton() { _recordAvailability = value; updateSendButtonType(); }, _send->lifetime()); + + _send->widthValue() | rpl::skip(1) | rpl::start_with_next([=] { + updateControlsGeometry(_wrap->size()); + }, _send->lifetime()); } void ComposeControls::initSendAsButton(not_null peer) { @@ -2550,14 +2554,18 @@ SendMenu::Details ComposeControls::sendButtonMenuDetails() const { void ComposeControls::updateSendButtonType() { using Type = Ui::SendButton::Type; const auto type = computeSendButtonType(); - _send->setType(type); - const auto delay = [&] { return (type != Type::Cancel && type != Type::Save) ? _slowmodeSecondsLeft.current() : 0; }(); - _send->setSlowmodeDelay(delay); + const auto peer = _history ? _history->peer.get() : nullptr; + const auto stars = peer ? peer->starsPerMessageChecked() : 0; + _send->setState({ + .type = type, + .slowmodeDelay = delay, + .starsToSend = stars, + }); _send->setDisabled(_sendDisabledBySlowmode.current() && (type == Type::Send || type == Type::Record diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 1f80fb15c..696a480b1 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -524,6 +524,7 @@ public: ListenWrap( not_null parent, const style::RecordBar &st, + std::shared_ptr send, not_null session, not_null data, const style::font &font); @@ -551,6 +552,7 @@ private: const not_null _parent; const style::RecordBar &_st; + const std::shared_ptr _send; const not_null _session; const not_null _document; const std::unique_ptr _voiceData; @@ -585,11 +587,13 @@ private: ListenWrap::ListenWrap( not_null parent, const style::RecordBar &st, + std::shared_ptr send, not_null session, not_null data, const style::font &font) : _parent(parent) , _st(st) +, _send(send) , _session(session) , _document(DummyDocument(&session->data())) , _voiceData(ProcessCaptureResult(data->waveform)) @@ -614,14 +618,18 @@ void ListenWrap::init() { }) | rpl::distinct_until_changed(); _delete->showOn(std::move(deleteShow)); - _parent->sizeValue( - ) | rpl::start_with_next([=](QSize size) { + rpl::combine( + _parent->sizeValue(), + _send->widthValue() + ) | rpl::start_with_next([=](QSize size, int send) { _waveformBgRect = QRect({ 0, 0 }, size) .marginsRemoved(st::historyRecordWaveformBgMargins); { - const auto m = _st.remove.width + _waveformBgRect.height() / 2; + const auto skip = _waveformBgRect.height() / 2; + const auto left = _st.remove.width + skip; + const auto right = send + skip; _waveformBgFinalCenterRect = _waveformBgRect.marginsRemoved( - style::margins(m, 0, m, 0)); + style::margins(left, 0, right, 0)); } { const auto &play = _playPauseSt.playOuter; @@ -651,7 +659,7 @@ void ListenWrap::init() { const auto deleteIconLeft = remove.iconPosition.x(); const auto bgRectRight = anim::interpolate( deleteIconLeft, - remove.width, + _send->width(), _isShowAnimation ? progress : 1.); const auto bgRectLeft = anim::interpolate( _parent->width() - deleteIconLeft - _waveformBgRect.height(), @@ -1973,6 +1981,7 @@ void VoiceRecordBar::stopRecording(StopType type, bool ttlBeforeHide) { _listen = std::make_unique( this, _st, + _send, &_show->session(), &_data, _cancelFont); @@ -2006,6 +2015,7 @@ void VoiceRecordBar::stopRecording(StopType type, bool ttlBeforeHide) { _listen = std::make_unique( this, _st, + _send, &_show->session(), &_data, _cancelFont); diff --git a/Telegram/SourceFiles/ui/controls/send_button.cpp b/Telegram/SourceFiles/ui/controls/send_button.cpp index e0388c032..5aaa78303 100644 --- a/Telegram/SourceFiles/ui/controls/send_button.cpp +++ b/Telegram/SourceFiles/ui/controls/send_button.cpp @@ -7,10 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/controls/send_button.h" +#include "lang/lang_tag.h" #include "ui/effects/ripple_animation.h" +#include "ui/text/text_utilities.h" #include "ui/painter.h" #include "ui/ui_utility.h" #include "styles/style_chat_helpers.h" +#include "styles/style_credits.h" // starIconEmoji namespace Ui { namespace { @@ -22,47 +25,58 @@ constexpr int kWideScale = 5; SendButton::SendButton(QWidget *parent, const style::SendButton &st) : RippleButton(parent, st.inner.ripple) , _st(st) { - resize(_st.inner.width, _st.inner.height); + updateSize(); } -void SendButton::setType(Type type) { - Expects(isSlowmode() || type != Type::Slowmode); - - if (isSlowmode() && type != Type::Slowmode) { - _afterSlowmodeType = type; +void SendButton::setState(State state) { + if (_state == state) { return; } - if (_type != type) { + const auto hasSlowmode = (_state.slowmodeDelay > 0); + const auto hasSlowmodeChanged = hasSlowmode != (state.slowmodeDelay > 0); + auto withSameSlowmode = state; + withSameSlowmode.slowmodeDelay = _state.slowmodeDelay; + const auto animate = hasSlowmodeChanged + || (!hasSlowmode && withSameSlowmode != _state); + if (animate) { _contentFrom = grabContent(); - _type = type; - _a_typeChanged.stop(); + } + if (_state.slowmodeDelay != state.slowmodeDelay) { + const auto seconds = state.slowmodeDelay; + const auto minutes = seconds / 60; + _slowmodeDelayText = seconds + ? u"%1:%2"_q.arg(minutes).arg(seconds % 60, 2, 10, QChar('0')) + : QString(); + } + if (!state.starsToSend || state.type != Type::Send) { + _starsToSendText = Text::String(); + } else if (_starsToSendText.isEmpty() + || _state.starsToSend != state.starsToSend) { + _starsToSendText.setMarkedText( + _st.stars.style, + Text::IconEmoji(&st::starIconEmoji).append( + Lang::FormatCountToShort(state.starsToSend).string), + kMarkupTextOptions); + } + _state = state; + if (animate) { + _stateChangeFromWidth = width(); + _stateChangeAnimation.stop(); + updateSize(); _contentTo = grabContent(); - _a_typeChanged.start( - [=] { update(); }, + _stateChangeAnimation.start( + [=] { updateSize(); update(); }, 0., 1., st::universalDuration); - setPointerCursor(_type != Type::Slowmode); + setPointerCursor(_state.type != Type::Slowmode); + updateSize(); update(); } } -void SendButton::setSlowmodeDelay(int seconds) { - Expects(seconds >= 0 && seconds < kSlowmodeDelayLimit); - - if (_slowmodeDelay == seconds) { - return; - } - _slowmodeDelay = seconds; - _slowmodeDelayText = isSlowmode() - ? u"%1:%2"_q.arg(seconds / 60).arg(seconds % 60, 2, 10, QChar('0')) - : QString(); - setType(isSlowmode() ? Type::Slowmode : _afterSlowmodeType); - update(); -} - void SendButton::finishAnimating() { - _a_typeChanged.stop(); + _stateChangeAnimation.stop(); update(); } @@ -70,26 +84,60 @@ void SendButton::paintEvent(QPaintEvent *e) { auto p = QPainter(this); auto over = (isDown() || isOver()); - auto changed = _a_typeChanged.value(1.); + auto changed = _stateChangeAnimation.value(1.); if (changed < 1.) { PainterHighQualityEnabler hq(p); + const auto ratio = style::DevicePixelRatio(); + p.setOpacity(1. - changed); - auto targetRect = QRect((1 - kWideScale) / 2 * width(), (1 - kWideScale) / 2 * height(), kWideScale * width(), kWideScale * height()); - auto hiddenWidth = anim::interpolate(0, (1 - kWideScale) / 2 * width(), changed); - auto hiddenHeight = anim::interpolate(0, (1 - kWideScale) / 2 * height(), changed); - p.drawPixmap(targetRect.marginsAdded(QMargins(hiddenWidth, hiddenHeight, hiddenWidth, hiddenHeight)), _contentFrom); + const auto fromSize = _contentFrom.size() / (kWideScale * ratio); + const auto fromShift = QPoint( + (width() - fromSize.width()) / 2, + (height() - fromSize.height()) / 2); + auto fromRect = QRect( + (1 - kWideScale) / 2 * fromSize.width(), + (1 - kWideScale) / 2 * fromSize.height(), + kWideScale * fromSize.width(), + kWideScale * fromSize.height() + ).translated(fromShift); + auto hiddenWidth = anim::interpolate(0, (1 - kWideScale) / 2 * fromSize.width(), changed); + auto hiddenHeight = anim::interpolate(0, (1 - kWideScale) / 2 * fromSize.height(), changed); + p.drawPixmap( + fromRect.marginsAdded( + { hiddenWidth, hiddenHeight, hiddenWidth, hiddenHeight }), + _contentFrom); + p.setOpacity(changed); + const auto toSize = _contentTo.size() / (kWideScale * ratio); + const auto toShift = QPoint( + (width() - toSize.width()) / 2, + (height() - toSize.height()) / 2); + auto toRect = QRect( + (1 - kWideScale) / 2 * toSize.width(), + (1 - kWideScale) / 2 * toSize.height(), + kWideScale * toSize.width(), + kWideScale * toSize.height() + ).translated(toShift); auto shownWidth = anim::interpolate((1 - kWideScale) / 2 * width(), 0, changed); - auto shownHeight = anim::interpolate((1 - kWideScale) / 2 * height(), 0, changed); - p.drawPixmap(targetRect.marginsAdded(QMargins(shownWidth, shownHeight, shownWidth, shownHeight)), _contentTo); + auto shownHeight = anim::interpolate((1 - kWideScale) / 2 * toSize.height(), 0, changed); + p.drawPixmap( + toRect.marginsAdded( + { shownWidth, shownHeight, shownWidth, shownHeight }), + _contentTo); return; } - switch (_type) { + switch (_state.type) { case Type::Record: paintRecord(p, over); break; case Type::Round: paintRound(p, over); break; case Type::Save: paintSave(p, over); break; case Type::Cancel: paintCancel(p, over); break; - case Type::Send: paintSend(p, over); break; + case Type::Send: + if (_starsToSendText.isEmpty()) { + paintSend(p, over); + } else { + paintStarsToSend(p, over); + } + break; case Type::Schedule: paintSchedule(p, over); break; case Type::Slowmode: paintSlowmode(p); break; } @@ -152,6 +200,23 @@ void SendButton::paintSend(QPainter &p, bool over) { } } +void SendButton::paintStarsToSend(QPainter &p, bool over) { + const auto geometry = starsGeometry(); + { + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(over ? _st.stars.textBgOver : _st.stars.textBg); + const auto radius = geometry.rounded.height() / 2; + p.drawRoundedRect(geometry.rounded, radius, radius); + } + p.setPen(over ? _st.stars.textFgOver : _st.stars.textFg); + _starsToSendText.draw(p, { + .position = geometry.inner.topLeft(), + .outerWidth = width(), + .availableWidth = geometry.inner.width(), + }); +} + void SendButton::paintSchedule(QPainter &p, bool over) { { PainterHighQualityEnabler hq(p); @@ -178,8 +243,40 @@ void SendButton::paintSlowmode(QPainter &p) { style::al_center); } -bool SendButton::isSlowmode() const { - return (_slowmodeDelay > 0); +SendButton::StarsGeometry SendButton::starsGeometry() const { + const auto &st = _st.stars; + const auto inner = QRect( + 0, + 0, + _starsToSendText.maxWidth(), + st.style.font->height); + const auto rounded = inner.marginsAdded(QMargins( + st.padding.left() - st.width / 2, + st.padding.top() + st.textTop, + st.padding.right() - st.width / 2, + st.height - st.padding.top() - st.textTop - st.style.font->height)); + const auto add = (_st.inner.height - rounded.height()) / 2; + const auto outer = rounded.marginsAdded(QMargins( + add, + add, + add, + _st.inner.height - add - rounded.height())); + const auto shift = -outer.topLeft(); + return { + .inner = inner.translated(shift), + .rounded = rounded.translated(shift), + .outer = outer.translated(shift), + }; +} + +void SendButton::updateSize() { + const auto finalWidth = _starsToSendText.isEmpty() + ? _st.inner.width + : starsGeometry().outer.width(); + const auto progress = _stateChangeAnimation.value(1.); + resize( + anim::interpolate(_stateChangeFromWidth, finalWidth, progress), + _st.inner.height); } QPixmap SendButton::grabContent() { @@ -195,7 +292,7 @@ QPixmap SendButton::grabContent() { (kWideScale - 1) / 2 * height(), GrabWidget(this)); } - return Ui::PixmapFromImage(std::move(result)); + return PixmapFromImage(std::move(result)); } QImage SendButton::prepareRippleMask() const { diff --git a/Telegram/SourceFiles/ui/controls/send_button.h b/Telegram/SourceFiles/ui/controls/send_button.h index 835974f51..b1483bbc4 100644 --- a/Telegram/SourceFiles/ui/controls/send_button.h +++ b/Telegram/SourceFiles/ui/controls/send_button.h @@ -30,11 +30,21 @@ public: Cancel, Slowmode, }; + struct State { + Type type = Type::Send; + int slowmodeDelay = 0; + int starsToSend = 0; + + friend inline constexpr auto operator<=>(State, State) = default; + friend inline constexpr bool operator==(State, State) = default; + }; [[nodiscard]] Type type() const { - return _type; + return _state.type; } - void setType(Type state); - void setSlowmodeDelay(int seconds); + [[nodiscard]] State state() const { + return _state; + } + void setState(State state); void finishAnimating(); protected: @@ -44,8 +54,15 @@ protected: QPoint prepareRippleStartPosition() const override; private: + struct StarsGeometry { + QRect inner; + QRect rounded; + QRect outer; + }; [[nodiscard]] QPixmap grabContent(); - [[nodiscard]] bool isSlowmode() const; + void updateSize(); + + [[nodiscard]] StarsGeometry starsGeometry() const; void paintRecord(QPainter &p, bool over); void paintRound(QPainter &p, bool over); @@ -54,17 +71,18 @@ private: void paintSend(QPainter &p, bool over); void paintSchedule(QPainter &p, bool over); void paintSlowmode(QPainter &p); + void paintStarsToSend(QPainter &p, bool over); const style::SendButton &_st; - Type _type = Type::Send; - Type _afterSlowmodeType = Type::Send; + State _state; QPixmap _contentFrom, _contentTo; - Ui::Animations::Simple _a_typeChanged; + Ui::Animations::Simple _stateChangeAnimation; + int _stateChangeFromWidth = 0; - int _slowmodeDelay = 0; QString _slowmodeDelayText; + Ui::Text::String _starsToSendText; }; From 63fdc1f8763bb76ad1fb1e24d441bcfca57299e8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 27 Feb 2025 21:27:40 +0400 Subject: [PATCH 064/103] Update API scheme on layer 200. --- Telegram/SourceFiles/mtproto/scheme/api.tl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index a86e910d9..830b26396 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -1839,7 +1839,7 @@ starsTransactionPeerAPI#f9677aad = StarsTransactionPeer; starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; -starsTransaction#ecd50924 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true premium_gift:flags.20?true id:string stars:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount paid_messages:flags.19?int = StarsTransaction; +starsTransaction#a39fd94a flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true id:string stars:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount paid_messages:flags.19?int premium_gift_months:flags.20?int = StarsTransaction; payments.starsStatus#6c9ce8ed flags:# balance:StarsAmount subscriptions:flags.1?Vector subscriptions_next_offset:flags.2?string subscriptions_missing_balance:flags.4?long history:flags.3?Vector next_offset:flags.0?string chats:Vector users:Vector = payments.StarsStatus; From b3f9a77ba7154d26ab6d7c8f950368f3bc488e9e Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 28 Feb 2025 12:08:26 +0400 Subject: [PATCH 065/103] Star-count button in SendFilesBox/ShareBox. --- .../boxes/peers/edit_peer_invite_link.cpp | 8 ++- .../SourceFiles/boxes/send_credits_box.cpp | 8 +-- Telegram/SourceFiles/boxes/send_files_box.cpp | 28 ++++++++- Telegram/SourceFiles/boxes/send_files_box.h | 2 + Telegram/SourceFiles/boxes/share_box.cpp | 59 ++++++++++++++++--- Telegram/SourceFiles/boxes/share_box.h | 9 ++- .../calls/group/calls_group_settings.cpp | 8 ++- .../chat_helpers/chat_helpers.style | 5 ++ .../media/stories/media_stories_share.cpp | 11 +++- .../SourceFiles/window/window_peer_menu.cpp | 15 +++-- Telegram/lib_ui | 2 +- 11 files changed, 128 insertions(+), 27 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp index 288a09353..38eb775f6 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp @@ -1484,9 +1484,12 @@ object_ptr ShareInviteLinkBox( ? tr::lng_group_invite_copied(tr::now) : copied); }; + auto countMessagesCallback = [=](const TextWithTags &comment) { + return 1; + }; auto submitCallback = [=]( std::vector> &&result, - Fn checkPaid, + Fn checkPaid, TextWithTags &&comment, Api::SendOptions options, Data::ForwardOptions) { @@ -1504,7 +1507,7 @@ object_ptr ShareInviteLinkBox( result.size() > 1)); } return; - } else if (!checkPaid(1)) { + } else if (!checkPaid()) { return; } @@ -1542,6 +1545,7 @@ object_ptr ShareInviteLinkBox( auto object = Box(ShareBox::Descriptor{ .session = session, .copyCallback = std::move(copyCallback), + .countMessagesCallback = std::move(countMessagesCallback), .submitCallback = std::move(submitCallback), .filterCallback = std::move(filterCallback), .moneyRestrictionError = ShareMessageMoneyRestrictionError(), diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index 204afadac..e7c9d0f9a 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/peer_bubble.h" #include "styles/style_boxes.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_credits.h" #include "styles/style_giveaway.h" #include "styles/style_info.h" // inviteLinkSubscribeBoxTerms @@ -511,11 +512,8 @@ TextWithEntities CreditsEmoji(not_null session) { } TextWithEntities CreditsEmojiSmall(not_null session) { - return Ui::Text::SingleCustomEmoji( - session->data().customEmojiManager().registerInternalEmoji( - st::starIconSmall, - st::starIconSmallPadding, - true), + return Ui::Text::IconEmoji( + &st::boxStarIconEmoji, QString(QChar(0x2B50))); } diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index b7e987b6e..b86be64c8 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -59,9 +59,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "core/application.h" #include "core/core_settings.h" -#include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" +#include "styles/style_layers.h" #include @@ -714,6 +714,18 @@ void SendFilesBox::openDialogToAddFileToAlbum() { crl::guard(this, callback)); } +void SendFilesBox::refreshMessagesCount() { + const auto way = _sendWay.current(); + const auto withCaption = _list.canAddCaption( + way.groupFiles() && way.sendImagesAsPhotos(), + way.sendImagesAsPhotos()); + const auto withComment = !withCaption + && _caption + && !_caption->isHidden() + && !_caption->getTextWithTags().text.isEmpty(); + _messagesCount = _list.files.size() + (withComment ? 1 : 0); +} + void SendFilesBox::refreshButtons() { clearButtons(); @@ -722,6 +734,19 @@ void SendFilesBox::refreshButtons() { ? tr::lng_send_button() : tr::lng_create_group_next()), [=] { send({}); }); + refreshMessagesCount(); + + const auto perMessage = _captionToPeer + ? _captionToPeer->starsPerMessageChecked() + : 0; + if (perMessage > 0) { + _send->setText(_messagesCount.value( + ) | rpl::map([=](int count) { + const auto stars = count * perMessage; + return Ui::Text::IconEmoji(&st::boxStarIconEmoji).append( + Lang::FormatCountToShort(stars).string); + })); + } if (_sendType == Api::SendType::Normal) { SendMenu::SetupMenuAndShortcuts( _send, @@ -1451,6 +1476,7 @@ void SendFilesBox::setupCaption() { _caption->changes() ) | rpl::start_with_next([=] { checkCharsLimitation(); + refreshMessagesCount(); }, _caption->lifetime()); } diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index 9a0fea06d..92ba0e3c3 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -246,6 +246,7 @@ private: void addPreparedAsyncFile(Ui::PreparedFile &&file); void checkCharsLimitation(); + void refreshMessagesCount(); [[nodiscard]] Fn prepareSendMenuDetails( const SendFilesBoxDescriptor &descriptor); @@ -261,6 +262,7 @@ private: Ui::PreparedList _list; std::optional _removingIndex; + rpl::variable _messagesCount; SendFilesLimits _limits = {}; Fn _sendMenuDetails; diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index e23dd7165..ad0f7ef97 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -54,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "core/core_settings.h" #include "styles/style_calls.h" +#include "styles/style_chat_helpers.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_menu_icons.h" @@ -620,6 +621,13 @@ void ShareBox::createButtons() { showMenu(send); } }, send->lifetime()); + send->setText(_starsToSend.value() | rpl::map([=](int stars) { + using namespace Ui; + return stars + ? Text::IconEmoji(&st::boxStarIconEmoji).append( + Lang::FormatCountToShort(stars).string) + : tr::lng_share_confirm(tr::now, Text::WithEntities); + })); } else if (_descriptor.copyCallback) { addButton(_copyLinkText.value(), [=] { copyLink(); }); } @@ -667,13 +675,19 @@ void ShareBox::submit(Api::SendOptions options) { auto threads = _inner->selected(); const auto weak = Ui::MakeWeak(this); - const auto checkPaid = [=](int messagesCount) { + const auto field = _comment->entity(); + auto comment = field->getTextWithAppliedMarkdown(); + const auto checkPaid = [=] { + if (!_descriptor.countMessagesCallback) { + return true; + } const auto withPaymentApproved = crl::guard(weak, [=](int approved) { auto copy = options; copy.starsApproved = approved; submit(copy); }); - + const auto messagesCount = _descriptor.countMessagesCallback( + comment); const auto alreadyApproved = options.starsApproved; auto paid = std::vector>(); auto waiting = base::flat_set>(); @@ -734,7 +748,7 @@ void ShareBox::submit(Api::SendOptions options) { onstack( std::move(threads), checkPaid, - _comment->entity()->getTextWithAppliedMarkdown(), + std::move(comment), options, forwardOptions); } @@ -754,9 +768,23 @@ void ShareBox::selectedChanged() { _comment->toggle(_hasSelected, anim::type::normal); _comment->resizeToWidth(st::boxWideWidth); } + computeStarsCount(); update(); } +void ShareBox::computeStarsCount() { + auto perMessage = 0; + for (const auto &thread : _inner->selected()) { + perMessage += thread->peer()->starsPerMessageChecked(); + } + const auto messagesCount = _descriptor.countMessagesCallback + ? _descriptor.countMessagesCallback(_comment + ? _comment->entity()->getTextWithTags() + : TextWithTags()) + : 0; + _starsToSend = perMessage * messagesCount; +} + void ShareBox::scrollTo(Ui::ScrollToRequest request) { scrollToY(request.ymin, request.ymax); //auto scrollTop = scrollArea()->scrollTop(), scrollBottom = scrollTop + scrollArea()->height(); @@ -1568,6 +1596,15 @@ ChatHelpers::ForwardedMessagePhraseArgs CreateForwardedMessagePhraseArgs( }; } +ShareBox::CountMessagesCallback ShareBox::DefaultForwardCountMessages( + not_null history, + MessageIdsList msgIds) { + return [=](const TextWithTags &comment) { + const auto items = history->owner().idsToItems(msgIds); + return int(items.size()) + (comment.empty() ? 0 : 1); + }; +} + ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( std::shared_ptr show, not_null history, @@ -1579,7 +1616,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( const auto state = std::make_shared(); return [=]( std::vector> &&result, - Fn checkPaid, + Fn checkPaid, TextWithTags comment, Api::SendOptions options, Data::ForwardOptions forwardOptions) { @@ -1593,14 +1630,13 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( return; } - auto messagesCount = int(items.size()) + (comment.empty() ? 0 : 1); const auto error = GetErrorForSending( result, { .forward = &items, .text = &comment }); if (error.error) { show->showBox(MakeSendErrorBox(error, result.size() > 1)); return; - } else if (!checkPaid(messagesCount)) { + } else if (!checkPaid()) { return; } @@ -1811,6 +1847,9 @@ void FastShareMessage( show->show(Box(ShareBox::Descriptor{ .session = session, .copyCallback = std::move(copyLinkCallback), + .countMessagesCallback = ShareBox::DefaultForwardCountMessages( + history, + msgIds), .submitCallback = ShareBox::DefaultForwardCallback( show, history, @@ -1850,9 +1889,12 @@ void FastShareLink( QGuiApplication::clipboard()->setText(url); show->showToast(tr::lng_background_link_copied(tr::now)); }; + auto countMessagesCallback = [=](const TextWithTags &comment) { + return 1; + }; auto submitCallback = [=]( std::vector> &&result, - Fn checkPaid, + Fn checkPaid, TextWithTags &&comment, Api::SendOptions options, ::Data::ForwardOptions) { @@ -1869,7 +1911,7 @@ void FastShareLink( MakeSendErrorBox(error, result.size() > 1)); } return; - } else if (!checkPaid(1)) { + } else if (!checkPaid()) { return; } @@ -1908,6 +1950,7 @@ void FastShareLink( Box(ShareBox::Descriptor{ .session = &show->session(), .copyCallback = std::move(copyCallback), + .countMessagesCallback = std::move(countMessagesCallback), .submitCallback = std::move(submitCallback), .filterCallback = std::move(filterCallback), .st = st, diff --git a/Telegram/SourceFiles/boxes/share_box.h b/Telegram/SourceFiles/boxes/share_box.h index 07227ee98..5cbfb794d 100644 --- a/Telegram/SourceFiles/boxes/share_box.h +++ b/Telegram/SourceFiles/boxes/share_box.h @@ -95,14 +95,18 @@ struct RecipientMoneyRestrictionError; class ShareBox final : public Ui::BoxContent { public: using CopyCallback = Fn; + using CountMessagesCallback = Fn; using SubmitCallback = Fn>&&, - Fn checkPaid, + Fn checkPaid, TextWithTags&&, Api::SendOptions, Data::ForwardOptions)>; using FilterCallback = Fn)>; + [[nodiscard]] static auto DefaultForwardCountMessages( + not_null history, + MessageIdsList msgIds) -> CountMessagesCallback; [[nodiscard]] static SubmitCallback DefaultForwardCallback( std::shared_ptr show, not_null history, @@ -112,6 +116,7 @@ public: struct Descriptor { not_null session; CopyCallback copyCallback; + CountMessagesCallback countMessagesCallback; SubmitCallback submitCallback; FilterCallback filterCallback; object_ptr bottomWidget = { nullptr }; @@ -152,6 +157,7 @@ private: void needSearchByUsername(); void applyFilterUpdate(const QString &query); void selectedChanged(); + void computeStarsCount(); void createButtons(); int getTopScrollSkip() const; int getBottomScrollSkip() const; @@ -183,6 +189,7 @@ private: bool _hasSelected = false; rpl::variable _copyLinkText; + rpl::variable _starsToSend; base::Timer _searchTimer; QString _peopleQuery; diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp index dec18b742..7c2bdfd4c 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp @@ -132,9 +132,12 @@ object_ptr ShareInviteLinkBox( QGuiApplication::clipboard()->setText(currentLink()); show->showToast(tr::lng_group_invite_copied(tr::now)); }; + auto countMessagesCallback = [=](const TextWithTags &comment) { + return 1; + }; auto submitCallback = [=]( std::vector> &&result, - Fn checkPaid, + Fn checkPaid, TextWithTags &&comment, Api::SendOptions options, Data::ForwardOptions) { @@ -151,7 +154,7 @@ object_ptr ShareInviteLinkBox( MakeSendErrorBox(error, result.size() > 1)); } return; - } else if (!checkPaid(1)) { + } else if (!checkPaid()) { return; } @@ -192,6 +195,7 @@ object_ptr ShareInviteLinkBox( auto result = Box(ShareBox::Descriptor{ .session = &peer->session(), .copyCallback = std::move(copyCallback), + .countMessagesCallback = std::move(countMessagesCallback), .submitCallback = std::move(submitCallback), .filterCallback = std::move(filterCallback), .bottomWidget = std::move(bottom), diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index d0d59bb5a..cf7eec9de 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1364,6 +1364,11 @@ defaultComposeControls: ComposeControls { restrictionLabel: defaultRestrictionLabel; } +boxStarIconEmoji: IconEmoji { + icon: icon{{ "payments/small_star", windowFg }}; + padding: margins(0px, -2px, 0px, 0px); +} + moreChatsBarHeight: 48px; moreChatsBarTextPosition: point(12px, 4px); moreChatsBarStatusPosition: point(12px, 24px); diff --git a/Telegram/SourceFiles/media/stories/media_stories_share.cpp b/Telegram/SourceFiles/media/stories/media_stories_share.cpp index d00da018c..80ec8e75b 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_share.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_share.cpp @@ -76,9 +76,12 @@ namespace Media::Stories { auto copyLinkCallback = canCopyLink ? Fn(std::move(copyCallback)) : Fn(); + auto countMessagesCallback = [=](const TextWithTags &comment) { + return comment.text.isEmpty() ? 1 : 2; + }; auto submitCallback = [=]( std::vector> &&result, - Fn checkPaid, + Fn checkPaid, TextWithTags &&comment, Api::SendOptions options, Data::ForwardOptions forwardOptions) { @@ -96,7 +99,7 @@ namespace Media::Stories { if (error.error) { show->showBox(MakeSendErrorBox(error, result.size() > 1)); return; - } else if (!checkPaid(comment.text.isEmpty() ? 1 : 2)) { + } else if (!checkPaid()) { return; } @@ -187,6 +190,7 @@ namespace Media::Stories { return Box(ShareBox::Descriptor{ .session = session, .copyCallback = std::move(copyLinkCallback), + .countMessagesCallback = std::move(countMessagesCallback), .submitCallback = std::move(submitCallback), .filterCallback = std::move(filterCallback), .st = st.shareBox ? *st.shareBox : ShareBoxStyleOverrides(), @@ -254,6 +258,9 @@ object_ptr PrepareShareAtTimeBox( return Box(ShareBox::Descriptor{ .session = session, .copyCallback = std::move(copyLinkCallback), + .countMessagesCallback = ShareBox::DefaultForwardCountMessages( + history, + { id }), .submitCallback = ShareBox::DefaultForwardCallback( show, history, diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 1e794a30f..7cf15e6ee 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -2463,15 +2463,21 @@ QPointer ShowForwardMessagesBox( tr::lng_photos_comment()), st::shareCommentPadding); + const auto history = session->data().message(msgIds.front())->history(); const auto send = ShareBox::DefaultForwardCallback( show, - session->data().message(msgIds.front())->history(), + history, + msgIds); + const auto countMessages = ShareBox::DefaultForwardCountMessages( + history, msgIds); const auto weak = Ui::MakeWeak(state->box); + const auto field = comment->entity(); state->submit = [=](Api::SendOptions options) { const auto peers = state->box->collectSelectedRows(); - const auto checkPaid = [=](int messagesCount) { + auto comment = field->getTextWithAppliedMarkdown(); + const auto checkPaid = [=] { const auto withPaymentApproved = crl::guard(weak, [=]( int approved) { auto copy = options; @@ -2482,6 +2488,7 @@ QPointer ShowForwardMessagesBox( }); const auto alreadyApproved = options.starsApproved; + const auto messagesCount = countMessages(comment); auto paid = std::vector>(); auto waiting = base::flat_set>(); auto totalStars = 0; @@ -2532,7 +2539,7 @@ QPointer ShowForwardMessagesBox( return peer->owner().history(peer); }) | ranges::to_vector, checkPaid, - comment->entity()->getTextWithAppliedMarkdown(), + std::move(comment), options, state->box->forwardOptionsData()); if (!state->submit && successCallback) { @@ -2628,8 +2635,6 @@ QPointer ShowForwardMessagesBox( state->box->setBottomSkip(comment->isHidden() ? 0 : commentHeight); }, comment->lifetime()); - const auto field = comment->entity(); - field->submits( ) | rpl::start_with_next([=] { if (const auto onstack = state->submit) { diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 2a5d66fb1..cfecbd3df 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 2a5d66fb1b9f97eacc3e73c324944a8d77c38e51 +Subproject commit cfecbd3df4c1d0bfba3d708dfb49387c8ae2127b From 97b021efaf66a67ca27ce41fd2e78570eeb3fd34 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 28 Feb 2025 12:57:39 +0400 Subject: [PATCH 066/103] Star-count button in multi-Forward/CreatePoll. --- Telegram/SourceFiles/boxes/boxes.style | 5 + .../SourceFiles/boxes/create_poll_box.cpp | 18 ++- Telegram/SourceFiles/boxes/create_poll_box.h | 2 + .../SourceFiles/boxes/send_credits_box.cpp | 1 - Telegram/SourceFiles/boxes/share_box.cpp | 1 - .../chat_helpers/chat_helpers.style | 5 - .../SourceFiles/window/window_peer_menu.cpp | 120 +++++++++++++----- 7 files changed, 111 insertions(+), 41 deletions(-) diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 827b0c0b3..90e59cd78 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -30,6 +30,11 @@ ShortInfoBox { labeledOneLine: FlatLabel; } +boxStarIconEmoji: IconEmoji { + icon: icon{{ "payments/small_star", windowFg }}; + padding: margins(0px, -2px, 0px, 0px); +} + countryRowHeight: 36px; countryRowNameFont: semiboldFont; countryRowNameFg: boxTextFg; diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp index 223c5cab4..517b1f5e3 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.cpp +++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp @@ -817,13 +817,15 @@ CreatePollBox::CreatePollBox( not_null controller, PollData::Flags chosen, PollData::Flags disabled, + rpl::producer starsRequired, Api::SendType sendType, SendMenu::Details sendMenuDetails) : _controller(controller) , _chosen(chosen) , _disabled(disabled) , _sendType(sendType) -, _sendMenuDetails([result = sendMenuDetails] { return result; }) { +, _sendMenuDetails([result = sendMenuDetails] { return result; }) +, _starsRequired(std::move(starsRequired)) { } rpl::producer CreatePollBox::submitRequests() const { @@ -1226,10 +1228,18 @@ object_ptr CreatePollBox::setupContent() { _sendMenuDetails()); }; const auto submit = addButton( - (isNormal - ? tr::lng_polls_create_button() - : tr::lng_schedule_button()), + tr::lng_polls_create_button(), [=] { isNormal ? send({}) : schedule(); }); + submit->setText(_starsRequired.value() | rpl::map([=](int stars) { + using namespace Ui; + if (!stars) { + return (isNormal + ? tr::lng_polls_create_button + : tr::lng_schedule_button)(tr::now, Text::WithEntities); + } + return Text::IconEmoji(&st::boxStarIconEmoji).append( + Lang::FormatCountToShort(stars).string); + })); const auto sendMenuDetails = [=] { collectError(); return (*error) ? SendMenu::Details() : _sendMenuDetails(); diff --git a/Telegram/SourceFiles/boxes/create_poll_box.h b/Telegram/SourceFiles/boxes/create_poll_box.h index 91fc290ca..33fbdd3f1 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.h +++ b/Telegram/SourceFiles/boxes/create_poll_box.h @@ -42,6 +42,7 @@ public: not_null controller, PollData::Flags chosen, PollData::Flags disabled, + rpl::producer starsRequired, Api::SendType sendType, SendMenu::Details sendMenuDetails); @@ -76,6 +77,7 @@ private: const PollData::Flags _disabled = PollData::Flags(); const Api::SendType _sendType = Api::SendType(); const Fn _sendMenuDetails; + rpl::variable _starsRequired; base::unique_qptr _emojiPanel; Fn _setInnerFocus; Fn()> _dataIsValidValue; diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index e7c9d0f9a..d1e74eeed 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -39,7 +39,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/peer_bubble.h" #include "styles/style_boxes.h" #include "styles/style_chat.h" -#include "styles/style_chat_helpers.h" #include "styles/style_credits.h" #include "styles/style_giveaway.h" #include "styles/style_info.h" // inviteLinkSubscribeBoxTerms diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index ad0f7ef97..5912dd312 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -54,7 +54,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "core/core_settings.h" #include "styles/style_calls.h" -#include "styles/style_chat_helpers.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_menu_icons.h" diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index cf7eec9de..d0d59bb5a 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1364,11 +1364,6 @@ defaultComposeControls: ComposeControls { restrictionLabel: defaultRestrictionLabel; } -boxStarIconEmoji: IconEmoji { - icon: icon{{ "payments/small_star", windowFg }}; - padding: margins(0px, -2px, 0px, 0px); -} - moreChatsBarHeight: 48px; moreChatsBarTextPosition: point(12px, 4px); moreChatsBarStatusPosition: point(12px, 24px); diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 7cf15e6ee..fd2c666f1 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1762,10 +1762,18 @@ void PeerMenuCreatePoll( chosen &= ~PollData::Flag::PublicVotes; disabled |= PollData::Flag::PublicVotes; } + auto starsRequired = peer->session().changes().peerFlagsValue( + peer, + Data::PeerUpdate::Flag::FullInfo + | Data::PeerUpdate::Flag::StarsPerMessage + ) | rpl::map([=] { + return peer->starsPerMessageChecked(); + }); auto box = Box( controller, chosen, disabled, + std::move(starsRequired), sendType, sendMenuDetails); struct State { @@ -1996,8 +2004,7 @@ object_ptr PrepareChooseRecipientBox( ChooseRecipientBoxController::rowClicked(row); } else { delegate()->peerListSetRowChecked(row, !row->checked()); - _hasSelectedChanges.fire( - delegate()->peerListSelectedRowsCount() > 0); + _selectionChanges.fire({}); } } @@ -2015,16 +2022,18 @@ object_ptr PrepareChooseRecipientBox( st::popupMenuWithIcons); menu->addAction(tr::lng_bot_choose_chat(tr::now), [=] { delegate()->peerListSetRowChecked(row, true); - _hasSelectedChanges.fire( - delegate()->peerListSelectedRowsCount() > 0); + _selectionChanges.fire({}); }, &st::menuIconSelect); return menu; } return nullptr; } - [[nodiscard]] rpl::producer hasSelectedChanges() const { - return _hasSelectedChanges.events_starting_with(false); + [[nodiscard]] rpl::producer<> selectionChanges() const { + return _selectionChanges.events_starting_with({}); + } + [[nodiscard]] bool hasSelected() const { + return delegate()->peerListSelectedRowsCount() > 0; } [[nodiscard]] rpl::producer singleChosen() const { @@ -2033,7 +2042,7 @@ object_ptr PrepareChooseRecipientBox( private: rpl::event_stream _singleChosen; - rpl::event_stream _hasSelectedChanges; + rpl::event_stream<> _selectionChanges; bool _selectable = false; }; @@ -2078,13 +2087,24 @@ object_ptr PrepareChooseRecipientBox( struct State { Fn submit; + rpl::variable starsToSend; + Fn refreshStarsToSend; rpl::lifetime submitLifetime; }; const auto state = std::make_shared(); auto initBox = [=](not_null box) { - raw->hasSelectedChanges( - ) | rpl::start_with_next([=](bool shown) { + state->refreshStarsToSend = [=] { + auto perMessage = 0; + for (const auto &peer : box->collectSelectedRows()) { + perMessage += peer->starsPerMessageChecked(); + } + state->starsToSend = perMessage; + }; + raw->selectionChanges( + ) | rpl::start_with_next([=] { box->clearButtons(); + state->refreshStarsToSend(); + const auto shown = raw->hasSelected(); if (shown) { const auto weak = Ui::MakeWeak(box); state->submit = [=](Api::SendOptions options) { @@ -2147,11 +2167,21 @@ object_ptr PrepareChooseRecipientBox( return peer->owner().history(peer); }) | ranges::to_vector, options); }; - box->addButton(tr::lng_send_button(), [=] { - if (const auto onstack = state->submit) { - onstack({}); - } - }); + const auto send = box->addButton( + tr::lng_send_button(), + [=] { + if (const auto onstack = state->submit) { + onstack({}); + } + }); + send->setText(state->starsToSend.value( + ) | rpl::map([=](int stars) { + using namespace Ui; + return stars + ? Text::IconEmoji(&st::boxStarIconEmoji).append( + Lang::FormatCountToShort(stars).string) + : tr::lng_send_button(tr::now, Text::WithEntities); + })); } box->addButton(tr::lng_cancel(), [=] { box->closeBox(); @@ -2282,8 +2312,7 @@ QPointer ShowForwardMessagesBox( ChooseRecipientBoxController::rowClicked(row); } else if (count) { delegate()->peerListSetRowChecked(row, !row->checked()); - _hasSelectedChanges.fire( - delegate()->peerListSelectedRowsCount() > 0); + _selectionChanges.fire({}); } } @@ -2296,16 +2325,18 @@ QPointer ShowForwardMessagesBox( st::popupMenuWithIcons); menu->addAction(tr::lng_bot_choose_chat(tr::now), [=] { delegate()->peerListSetRowChecked(row, true); - _hasSelectedChanges.fire( - delegate()->peerListSelectedRowsCount() > 0); + _selectionChanges.fire({}); }, &st::menuIconSelect); return menu; } return nullptr; } - [[nodiscard]] rpl::producer hasSelectedChanges() const { - return _hasSelectedChanges.events_starting_with(false); + [[nodiscard]] rpl::producer<> selectionChanges() const { + return _selectionChanges.events_starting_with({}); + } + [[nodiscard]] bool hasSelected() const { + return delegate()->peerListSelectedRowsCount() > 0; } [[nodiscard]] rpl::producer singleChosen() const{ @@ -2314,7 +2345,7 @@ QPointer ShowForwardMessagesBox( private: rpl::event_stream _singleChosen; - rpl::event_stream _hasSelectedChanges; + rpl::event_stream<> _selectionChanges; }; @@ -2323,6 +2354,8 @@ QPointer ShowForwardMessagesBox( not_null controller; base::unique_qptr menu; Fn submit; + rpl::variable starsToSend; + Fn refreshStarsToSend; rpl::lifetime submitLifetime; }; @@ -2622,8 +2655,20 @@ QPointer ShowForwardMessagesBox( } }; + state->refreshStarsToSend = [=] { + auto perMessage = 0; + for (const auto &peer : state->box->collectSelectedRows()) { + perMessage += peer->starsPerMessageChecked(); + } + state->starsToSend = perMessage + * countMessages(field->getTextWithTags()); + }; + comment->hide(anim::type::instant); - comment->toggleOn(state->controller->hasSelectedChanges()); + comment->toggleOn(state->controller->selectionChanges( + ) | rpl::map([=] { + return state->controller->hasSelected(); + })); rpl::combine( state->box->sizeValue(), @@ -2650,6 +2695,9 @@ QPointer ShowForwardMessagesBox( }, }); field->setSubmitSettings(Core::App().settings().sendSubmitWay()); + field->changes() | rpl::start_with_next([=] { + state->refreshStarsToSend(); + }, field->lifetime()); Ui::SendPendingMoveResizeEvents(comment); @@ -2660,16 +2708,20 @@ QPointer ShowForwardMessagesBox( } }, comment->lifetime()); - state->controller->hasSelectedChanges( - ) | rpl::start_with_next([=](bool shown) { + state->controller->selectionChanges( + ) | rpl::start_with_next([=] { + const auto shown = state->controller->hasSelected(); + state->box->clearButtons(); + state->refreshStarsToSend(); if (shown) { - auto text = tr::lng_send_button(); - const auto send = state->box->addButton(std::move(text), [=] { - if (const auto onstack = state->submit) { - onstack({}); - } - }); + const auto send = state->box->addButton( + tr::lng_send_button(), + [=] { + if (const auto onstack = state->submit) { + onstack({}); + } + }); send->setAcceptBoth(); send->clicks( ) | rpl::start_with_next([=](Qt::MouseButton button) { @@ -2677,6 +2729,14 @@ QPointer ShowForwardMessagesBox( showMenu(send); } }, send->lifetime()); + send->setText(state->starsToSend.value( + ) | rpl::map([=](int stars) { + using namespace Ui; + return stars + ? Text::IconEmoji(&st::boxStarIconEmoji).append( + Lang::FormatCountToShort(stars).string) + : tr::lng_send_button(tr::now, Text::WithEntities); + })); } state->box->addButton(tr::lng_cancel(), [=] { state->box->closeBox(); From 8ea7bd4913623bf2f1424fdcb0b6f9b7a7401dfc Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 28 Feb 2025 14:35:24 +0400 Subject: [PATCH 067/103] Simplify paid message button labeling. --- .../SourceFiles/boxes/create_poll_box.cpp | 13 +++-------- Telegram/SourceFiles/boxes/send_files_box.cpp | 8 ++----- Telegram/SourceFiles/boxes/share_box.cpp | 10 +++----- .../chat_helpers/message_field.cpp | 23 +++++++++++++++++++ .../SourceFiles/chat_helpers/message_field.h | 9 ++++++++ .../SourceFiles/ui/controls/send_button.cpp | 4 ++-- .../SourceFiles/window/window_peer_menu.cpp | 22 +++++------------- 7 files changed, 48 insertions(+), 41 deletions(-) diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp index 517b1f5e3..fab9731cc 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.cpp +++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp @@ -1230,16 +1230,9 @@ object_ptr CreatePollBox::setupContent() { const auto submit = addButton( tr::lng_polls_create_button(), [=] { isNormal ? send({}) : schedule(); }); - submit->setText(_starsRequired.value() | rpl::map([=](int stars) { - using namespace Ui; - if (!stars) { - return (isNormal - ? tr::lng_polls_create_button - : tr::lng_schedule_button)(tr::now, Text::WithEntities); - } - return Text::IconEmoji(&st::boxStarIconEmoji).append( - Lang::FormatCountToShort(stars).string); - })); + submit->setText(PaidSendButtonText(_starsRequired.value(), isNormal + ? tr::lng_polls_create_button() + : tr::lng_schedule_button())); const auto sendMenuDetails = [=] { collectError(); return (*error) ? SendMenu::Details() : _sendMenuDetails(); diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index b86be64c8..a269f83bd 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -740,12 +740,8 @@ void SendFilesBox::refreshButtons() { ? _captionToPeer->starsPerMessageChecked() : 0; if (perMessage > 0) { - _send->setText(_messagesCount.value( - ) | rpl::map([=](int count) { - const auto stars = count * perMessage; - return Ui::Text::IconEmoji(&st::boxStarIconEmoji).append( - Lang::FormatCountToShort(stars).string); - })); + _send->setText(PaidSendButtonText(_messagesCount.value( + ) | rpl::map(rpl::mappers::_1 * perMessage))); } if (_sendType == Api::SendType::Normal) { SendMenu::SetupMenuAndShortcuts( diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 5912dd312..c262435b7 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -620,13 +620,9 @@ void ShareBox::createButtons() { showMenu(send); } }, send->lifetime()); - send->setText(_starsToSend.value() | rpl::map([=](int stars) { - using namespace Ui; - return stars - ? Text::IconEmoji(&st::boxStarIconEmoji).append( - Lang::FormatCountToShort(stars).string) - : tr::lng_share_confirm(tr::now, Text::WithEntities); - })); + send->setText(PaidSendButtonText( + _starsToSend.value(), + tr::lng_share_confirm())); } else if (_descriptor.copyCallback) { addButton(_copyLinkText.value(), [=] { copyLink(); }); } diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 0177e3bfe..617051762 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -1280,3 +1280,26 @@ void SelectTextInFieldWithMargins( textCursor.setPosition(selection.to, QTextCursor::KeepAnchor); field->setTextCursor(textCursor); } + +TextWithEntities PaidSendButtonText(tr::now_t, int stars) { + return Ui::Text::IconEmoji(&st::boxStarIconEmoji).append( + Lang::FormatCountToShort(stars).string); +} + +rpl::producer PaidSendButtonText( + rpl::producer stars, + rpl::producer fallback) { + if (fallback) { + return rpl::combine( + std::move(fallback), + std::move(stars) + ) | rpl::map([=](QString zero, int count) { + return count + ? PaidSendButtonText(tr::now, count) + : TextWithEntities{ zero }; + }); + } + return std::move(stars) | rpl::map([=](int count) { + return PaidSendButtonText(tr::now, count); + }); +} diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h index 15e5f04b6..7d97d9332 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.h +++ b/Telegram/SourceFiles/chat_helpers/message_field.h @@ -19,6 +19,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include +namespace tr { +struct now_t; +} // namespace tr + namespace Main { class Session; class SessionShow; @@ -169,3 +173,8 @@ private: void SelectTextInFieldWithMargins( not_null field, const TextSelection &selection); + +[[nodiscard]] TextWithEntities PaidSendButtonText(tr::now_t, int stars); +[[nodiscard]] rpl::producer PaidSendButtonText( + rpl::producer stars, + rpl::producer fallback = nullptr); diff --git a/Telegram/SourceFiles/ui/controls/send_button.cpp b/Telegram/SourceFiles/ui/controls/send_button.cpp index 5aaa78303..8c692403b 100644 --- a/Telegram/SourceFiles/ui/controls/send_button.cpp +++ b/Telegram/SourceFiles/ui/controls/send_button.cpp @@ -12,8 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/painter.h" #include "ui/ui_utility.h" +#include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" -#include "styles/style_credits.h" // starIconEmoji namespace Ui { namespace { @@ -54,7 +54,7 @@ void SendButton::setState(State state) { || _state.starsToSend != state.starsToSend) { _starsToSendText.setMarkedText( _st.stars.style, - Text::IconEmoji(&st::starIconEmoji).append( + Text::IconEmoji(&st::boxStarIconEmoji).append( Lang::FormatCountToShort(state.starsToSend).string), kMarkupTextOptions); } diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index fd2c666f1..634c3c6f9 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -2174,14 +2174,9 @@ object_ptr PrepareChooseRecipientBox( onstack({}); } }); - send->setText(state->starsToSend.value( - ) | rpl::map([=](int stars) { - using namespace Ui; - return stars - ? Text::IconEmoji(&st::boxStarIconEmoji).append( - Lang::FormatCountToShort(stars).string) - : tr::lng_send_button(tr::now, Text::WithEntities); - })); + send->setText(PaidSendButtonText( + state->starsToSend.value(), + tr::lng_send_button())); } box->addButton(tr::lng_cancel(), [=] { box->closeBox(); @@ -2729,14 +2724,9 @@ QPointer ShowForwardMessagesBox( showMenu(send); } }, send->lifetime()); - send->setText(state->starsToSend.value( - ) | rpl::map([=](int stars) { - using namespace Ui; - return stars - ? Text::IconEmoji(&st::boxStarIconEmoji).append( - Lang::FormatCountToShort(stars).string) - : tr::lng_send_button(tr::now, Text::WithEntities); - })); + send->setText(PaidSendButtonText( + state->starsToSend.value(), + tr::lng_send_button())); } state->box->addButton(tr::lng_cancel(), [=] { state->box->closeBox(); From 9032489786f7cb39b1ddbf31dcf5c558bc5dbb22 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 28 Feb 2025 17:26:02 +0400 Subject: [PATCH 068/103] Add pays-me status bar in chat. --- .../chat_helpers/chat_helpers.style | 4 + Telegram/SourceFiles/data/data_changes.h | 31 ++-- Telegram/SourceFiles/data/data_peer.cpp | 34 +++- Telegram/SourceFiles/data/data_peer.h | 3 + .../SourceFiles/history/history_widget.cpp | 29 +++- Telegram/SourceFiles/history/history_widget.h | 2 + .../view/history_view_contact_status.cpp | 164 +++++++++++++++++- .../view/history_view_contact_status.h | 37 +++- 8 files changed, 284 insertions(+), 20 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index d0d59bb5a..8d265ab8a 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -918,6 +918,10 @@ historyBusinessBotSettings: IconButton(defaultIconButton) { height: 58px; width: 48px; } +paysStatusLabel: FlatLabel(historyBusinessBotStatus) { + align: align(top); + minWidth: 240px; +} historyReplyCancelIcon: icon {{ "box_button_close", historyReplyCancelFg }}; historyReplyCancelIconOver: icon {{ "box_button_close", historyReplyCancelFgOver }}; diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index 9e19fc617..f44ae72bb 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -95,27 +95,28 @@ struct PeerUpdate { Birthday = (1ULL << 33), PersonalChannel = (1ULL << 34), StarRefProgram = (1ULL << 35), + PaysPerMessage = (1ULL << 36), // For chats and channels - InviteLinks = (1ULL << 36), - Members = (1ULL << 37), - Admins = (1ULL << 38), - BannedUsers = (1ULL << 39), - Rights = (1ULL << 40), - PendingRequests = (1ULL << 41), - Reactions = (1ULL << 42), + InviteLinks = (1ULL << 37), + Members = (1ULL << 38), + Admins = (1ULL << 39), + BannedUsers = (1ULL << 40), + Rights = (1ULL << 41), + PendingRequests = (1ULL << 42), + Reactions = (1ULL << 43), // For channels - ChannelAmIn = (1ULL << 43), - StickersSet = (1ULL << 44), - EmojiSet = (1ULL << 45), - ChannelLinkedChat = (1ULL << 46), - ChannelLocation = (1ULL << 47), - Slowmode = (1ULL << 48), - GroupCall = (1ULL << 49), + ChannelAmIn = (1ULL << 44), + StickersSet = (1ULL << 45), + EmojiSet = (1ULL << 46), + ChannelLinkedChat = (1ULL << 47), + ChannelLocation = (1ULL << 48), + Slowmode = (1ULL << 49), + GroupCall = (1ULL << 50), // For iteration - LastUsedBit = (1ULL << 49), + LastUsedBit = (1ULL << 50), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index ad9449593..988c12dd8 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -734,7 +734,7 @@ void PeerData::checkFolder(FolderId folderId) { void PeerData::clearBusinessBot() { if (const auto details = _barDetails.get()) { - if (details->requestChatDate) { + if (details->requestChatDate || details->paysPerMessage) { details->businessBot = nullptr; details->businessBotManageUrl = QString(); } else { @@ -777,7 +777,10 @@ void PeerData::saveTranslationDisabled(bool disabled) { void PeerData::setBarSettings(const MTPPeerSettings &data) { data.match([&](const MTPDpeerSettings &data) { - if (!data.vbusiness_bot_id() && !data.vrequest_chat_title()) { + const auto wasPaysPerMessage = paysPerMessage(); + if (!data.vbusiness_bot_id() + && !data.vrequest_chat_title() + && !data.vcharge_paid_message_stars()) { _barDetails = nullptr; } else if (!_barDetails) { _barDetails = std::make_unique(); @@ -792,6 +795,8 @@ void PeerData::setBarSettings(const MTPPeerSettings &data) { : nullptr; _barDetails->businessBotManageUrl = qs(data.vbusiness_bot_manage_url().value_or_empty()); + _barDetails->paysPerMessage + = data.vcharge_paid_message_stars().value_or_empty(); } using Flag = PeerBarSetting; setBarSettings((data.is_add_contact() ? Flag::AddContact : Flag()) @@ -815,8 +820,33 @@ void PeerData::setBarSettings(const MTPPeerSettings &data) { | (data.is_business_bot_can_reply() ? Flag::BusinessBotCanReply : Flag())); + if (wasPaysPerMessage != paysPerMessage()) { + session().changes().peerUpdated( + this, + UpdateFlag::PaysPerMessage); + } }); } + +int PeerData::paysPerMessage() const { + return _barDetails ? _barDetails->paysPerMessage : 0; +} + +void PeerData::clearPaysPerMessage() { + if (const auto details = _barDetails.get()) { + if (details->paysPerMessage) { + if (details->businessBot || details->requestChatDate) { + details->paysPerMessage = 0; + } else { + _barDetails = nullptr; + } + session().changes().peerUpdated( + this, + UpdateFlag::PaysPerMessage); + } + } +} + QString PeerData::requestChatTitle() const { return _barDetails ? _barDetails->requestChatTitle : QString(); } diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index dcec75edf..d0b6d687a 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -177,6 +177,7 @@ struct PeerBarDetails { TimeId requestChatDate; UserData *businessBot = nullptr; QString businessBotManageUrl; + int paysPerMessage = 0; }; class PeerData { @@ -412,6 +413,8 @@ public: ? _barSettings.changes() : (_barSettings.value() | rpl::type_erased()); } + [[nodiscard]] int paysPerMessage() const; + void clearPaysPerMessage(); [[nodiscard]] QString requestChatTitle() const; [[nodiscard]] TimeId requestChatDate() const; [[nodiscard]] UserData *businessBot() const; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index c862ccc5e..c7c55f463 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -1693,6 +1693,9 @@ void HistoryWidget::orderWidgets() { if (_contactStatus) { _contactStatus->bar().raise(); } + if (_paysStatus) { + _paysStatus->bar().raise(); + } if (_translateBar) { _translateBar->raise(); } @@ -2416,6 +2419,7 @@ void HistoryWidget::showHistory( _showAtMsgId = showAtMsgId; _showAtMsgParams = params; _historyInited = false; + _paysStatus = nullptr; _contactStatus = nullptr; _businessBotStatus = nullptr; @@ -2436,6 +2440,14 @@ void HistoryWidget::showHistory( refreshGiftToChannelShown(); if (const auto user = _peer->asUser()) { + _paysStatus = std::make_unique( + controller(), + this, + user); + _paysStatus->bar().heightValue( + ) | rpl::start_with_next([=] { + updateControlsGeometry(); + }, _paysStatus->bar().lifetime()); _businessBotStatus = std::make_unique( controller(), this, @@ -3077,6 +3089,9 @@ void HistoryWidget::updateControlsVisibility() { if (_requestsBar) { _requestsBar->show(); } + if (_paysStatus) { + _paysStatus->show(); + } if (_contactStatus) { _contactStatus->show(); } @@ -4305,6 +4320,9 @@ void HistoryWidget::hideChildWidgets() { if (_chooseTheme) { _chooseTheme->hide(); } + if (_paysStatus) { + _paysStatus->hide(); + } if (_contactStatus) { _contactStatus->hide(); } @@ -6266,8 +6284,13 @@ void HistoryWidget::updateControlsGeometry() { _translateBar->move(0, translateTop); _translateBar->resizeToWidth(width()); } - const auto contactStatusTop = translateTop + const auto paysStatusTop = translateTop + (_translateBar ? _translateBar->height() : 0); + if (_paysStatus) { + _paysStatus->bar().move(0, paysStatusTop); + } + const auto contactStatusTop = paysStatusTop + + (_paysStatus ? _paysStatus->bar().height() : 0); if (_contactStatus) { _contactStatus->bar().move(0, contactStatusTop); } @@ -6518,6 +6541,9 @@ void HistoryWidget::updateHistoryGeometry( if (_requestsBar) { newScrollHeight -= _requestsBar->height(); } + if (_paysStatus) { + newScrollHeight -= _paysStatus->bar().height(); + } if (_contactStatus) { newScrollHeight -= _contactStatus->bar().height(); } @@ -6940,6 +6966,7 @@ void HistoryWidget::botCallbackSent(not_null item) { int HistoryWidget::computeMaxFieldHeight() const { const auto available = height() - _topBar->height() + - (_paysStatus ? _paysStatus->bar().height() : 0) - (_contactStatus ? _contactStatus->bar().height() : 0) - (_businessBotStatus ? _businessBotStatus->bar().height() : 0) - (_sponsoredMessageBar ? _sponsoredMessageBar->height() : 0) diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 2b956c2c1..e8dbd4f8c 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -100,6 +100,7 @@ namespace HistoryView { class StickerToast; class PaidReactionToast; class TopBarWidget; +class PaysStatus; class ContactStatus; class BusinessBotStatus; class Element; @@ -782,6 +783,7 @@ private: Webrtc::RecordAvailability _recordAvailability = {}; + std::unique_ptr _paysStatus; std::unique_ptr _contactStatus; std::unique_ptr _businessBotStatus; diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp index a25ebeed0..60e1b4e86 100644 --- a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp +++ b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/boxes/confirm_box.h" #include "ui/layers/generic_box.h" +#include "chat_helpers/message_field.h" // PaidSendButtonText #include "core/click_handler_types.h" #include "core/ui_integration.h" #include "data/business/data_business_chatbots.h" @@ -595,9 +596,9 @@ auto ContactStatus::PeerState(not_null peer) return { .type = Type::RequestChatInfo, .requestChatName = peer->requestChatTitle(), + .requestDate = peer->requestChatDate(), .requestChatIsBroadcast = !!(settings.value & PeerBarSetting::RequestChatIsBroadcast), - .requestDate = peer->requestChatDate(), }; } else if (settings.value & PeerBarSetting::AutoArchived) { return { Type::UnarchiveOrBlock }; @@ -1131,4 +1132,165 @@ void TopicReopenBar::setupHandler() { }); } +class PaysStatus::Bar final : public Ui::RpWidget { +public: + Bar(QWidget *parent, not_null peer); + + void showState(State state); + + [[nodiscard]] rpl::producer<> removeClicks() const; + +private: + void paintEvent(QPaintEvent *e) override; + int resizeGetHeight(int newWidth) override; + + not_null _peer; + object_ptr _label; + object_ptr _remove; + rpl::event_stream<> _removeClicks; + +}; + +PaysStatus::Bar::Bar(QWidget *parent, not_null peer) +: RpWidget(parent) +, _peer(peer) +, _label(this, st::paysStatusLabel) +, _remove(this, tr::lng_payment_bar_button(tr::now)) { + _label->setAttribute(Qt::WA_TransparentForMouseEvents); +} + +void PaysStatus::Bar::showState(State state) { + _label->setMarkedText(tr::lng_payment_bar_text( + tr::now, + lt_name, + TextWithEntities{ _peer->shortName() }, + lt_cost, + PaidSendButtonText(tr::now, state.perMessage), + Ui::Text::WithEntities)); + resizeToWidth(width()); +} + +rpl::producer<> PaysStatus::Bar::removeClicks() const { + return _remove->clicks() | rpl::to_empty; +} + +void PaysStatus::Bar::paintEvent(QPaintEvent *e) { + QPainter p(this); + p.fillRect(e->rect(), st::historyContactStatusButton.bgColor); +} + +int PaysStatus::Bar::resizeGetHeight(int newWidth) { + const auto skip = st::defaultPeerListItem.photoPosition.y(); + _label->resizeToWidth(newWidth - skip); + _label->moveToLeft(skip, skip, newWidth); + _remove->move( + (newWidth - _remove->width()) / 2, + skip + _label->height() + skip); + return _remove->y() + _remove->height() + skip; +} + +PaysStatus::PaysStatus( + not_null window, + not_null parent, + not_null user) +: _controller(window) +, _user(user) +, _inner(Ui::CreateChild(parent.get(), user)) +, _bar(parent, object_ptr::fromRaw(_inner)) { + setupState(); + setupHandlers(); +} + +void PaysStatus::setupState() { + _user->session().api().requestPeerSettings(_user); + + _user->session().changes().peerFlagsValue( + _user, + Data::PeerUpdate::Flag::PaysPerMessage + ) | rpl::start_with_next([=] { + _state = State{ _user->paysPerMessage() }; + if (_state.perMessage > 0) { + _inner->showState(_state); + _bar.toggleContent(true); + } else { + _bar.toggleContent(false); + } + }, _bar.lifetime()); +} + +void PaysStatus::setupHandlers() { + _inner->removeClicks( + ) | rpl::start_with_next([=] { + const auto user = _user; + const auto exception = [=](bool refund) { + using Flag = MTPaccount_AddNoPaidMessagesException::Flag; + const auto api = &user->session().api(); + api->request(MTPaccount_AddNoPaidMessagesException( + MTP_flags(refund ? Flag::f_refund_charged : Flag()), + user->inputUser + )).done([=] { + user->clearPaysPerMessage(); + }).send(); + }; + _controller->show(Box([=](not_null box) { + const auto refund = std::make_shared>(); + Ui::ConfirmBox(box, { + .text = tr::lng_payment_refund_text( + tr::now, + lt_name, + Ui::Text::Bold(user->shortName()), + Ui::Text::WithEntities), + .confirmed = [=](Fn close) { + exception(*refund && (*refund)->checked()); + close(); + }, + .confirmText = tr::lng_payment_refund_confirm(tr::now), + .title = tr::lng_payment_refund_title(tr::now), + }); + const auto paid = box->lifetime().make_state< + rpl::variable + >(); + *paid = _paidAlready.value(); + paid->value() | rpl::start_with_next([=](int already) { + if (!already) { + delete base::take(*refund); + } else if (!*refund) { + const auto skip = st::defaultCheckbox.margin.top(); + *refund = box->addRow( + object_ptr( + box, + tr::lng_payment_refund_also( + lt_count, + paid->value() | tr::to_count()), + false, + st::defaultCheckbox), + st::boxRowPadding + QMargins(0, skip, 0, skip)); + } + }, box->lifetime()); + + user->session().api().request(MTPaccount_GetPaidMessagesRevenue( + user->inputUser + )).done(crl::guard(_inner, [=]( + const MTPaccount_PaidMessagesRevenue &result) { + _paidAlready = result.data().vstars_amount().v; + })).send(); + })); + }, _bar.lifetime()); +} + +void PaysStatus::show() { + if (!_shown) { + _shown = true; + if (_state.perMessage > 0) { + _inner->showState(_state); + _bar.toggleContent(true); + } + } + _bar.show(); +} + +void PaysStatus::hide() { + _bar.hide(); +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.h b/Telegram/SourceFiles/history/view/history_view_contact_status.h index aafc23a6a..56be475a8 100644 --- a/Telegram/SourceFiles/history/view/history_view_contact_status.h +++ b/Telegram/SourceFiles/history/view/history_view_contact_status.h @@ -95,9 +95,10 @@ private: RequestChatInfo, }; Type type = Type::None; + int starsPerMessage = 0; QString requestChatName; - bool requestChatIsBroadcast = false; TimeId requestDate = 0; + bool requestChatIsBroadcast = false; }; void setupState(not_null peer, bool showInForum); @@ -181,4 +182,38 @@ private: }; +class PaysStatus final { +public: + PaysStatus( + not_null controller, + not_null parent, + not_null user); + + void show(); + void hide(); + + [[nodiscard]] SlidingBar &bar() { + return _bar; + } + +private: + class Bar; + + struct State { + int perMessage = 0; + }; + + void setupState(); + void setupHandlers(); + + const not_null _controller; + const not_null _user; + rpl::variable _paidAlready; + State _state; + QPointer _inner; + SlidingBar _bar; + bool _shown = false; + +}; + } // namespace HistoryView From 827040f48772e813d125e86c73183cff5b969835 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Mar 2025 09:22:07 +0400 Subject: [PATCH 069/103] Use paid messages values from appConfig. --- .../SourceFiles/boxes/edit_privacy_box.cpp | 155 +++++++++--------- .../boxes/peers/edit_peer_permissions_box.cpp | 53 +++--- .../SourceFiles/data/components/credits.cpp | 5 +- Telegram/SourceFiles/data/data_channel.cpp | 6 +- Telegram/SourceFiles/data/data_channel.h | 4 + Telegram/SourceFiles/main/main_app_config.cpp | 16 ++ Telegram/SourceFiles/main/main_app_config.h | 5 + 7 files changed, 142 insertions(+), 102 deletions(-) diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp index eba2b0e3a..5c7463261 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp @@ -45,9 +45,7 @@ namespace { constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value; constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value; -constexpr auto kGetPercent = 85; constexpr auto kStarsMin = 1; -constexpr auto kStarsMax = 10000; constexpr auto kDefaultChargeStars = 10; using Exceptions = Api::UserPrivacy::Exceptions; @@ -466,6 +464,7 @@ auto PrivacyExceptionsBoxController::createRow(not_null history) int valuesCount, Fn valueByIndex, int value, + int maxValue, Fn valueProgress, Fn valueFinished) { auto result = object_ptr(parent); @@ -478,7 +477,7 @@ auto PrivacyExceptionsBoxController::createRow(not_null history) *labelStyle); const auto max = Ui::CreateChild( raw, - QString::number(kStarsMax), + QString::number(maxValue), *labelStyle); const auto current = Ui::CreateChild( raw, @@ -999,28 +998,22 @@ void EditMessagesPrivacyBox( Ui::AddDividerText(inner, tr::lng_messages_privacy_about()); - const auto charged = inner->add( - object_ptr( - inner, - group, - kOptionCharge, - tr::lng_messages_privacy_charge(tr::now), - st::messagePrivacyCheck), - st::settingsSendTypePadding + style::margins( - 0, - st::messagePrivacyBottomSkip, - 0, - st::messagePrivacyBottomSkip)); + const auto available = session->appConfig().paidMessagesAvailable(); - Ui::AddDividerText(inner, tr::lng_messages_privacy_charge_about()); - - const auto chargeWrap = inner->add( - object_ptr>( - inner, - object_ptr(inner))); - const auto chargeInner = chargeWrap->entity(); - - Ui::AddSkip(chargeInner); + const auto charged = available + ? inner->add( + object_ptr( + inner, + group, + kOptionCharge, + tr::lng_messages_privacy_charge(tr::now), + st::messagePrivacyCheck), + st::settingsSendTypePadding + style::margins( + 0, + st::messagePrivacyBottomSkip, + 0, + st::messagePrivacyBottomSkip)) + : nullptr; struct State { rpl::variable stars; @@ -1028,54 +1021,67 @@ void EditMessagesPrivacyBox( const auto state = std::make_shared(); const auto savedValue = privacy->newChargeStarsCurrent(); - state->stars = SetupChargeSlider( - chargeInner, - session->user(), - savedValue); + if (available) { + Ui::AddDividerText(inner, tr::lng_messages_privacy_charge_about()); - Ui::AddSkip(chargeInner); - Ui::AddSubsectionTitle( - chargeInner, - tr::lng_messages_privacy_exceptions()); + const auto chargeWrap = inner->add( + object_ptr>( + inner, + object_ptr(inner))); + const auto chargeInner = chargeWrap->entity(); - const auto key = Api::UserPrivacy::Key::NoPaidMessages; - session->api().userPrivacy().reload(key); - auto label = session->api().userPrivacy().value( - key - ) | rpl::map([=](const Api::UserPrivacy::Rule &value) { - using namespace Settings; - const auto always = ExceptionUsersCount(value.always.peers); - return always - ? tr::lng_edit_privacy_exceptions_count( - tr::now, - lt_count, - always) - : QString(); - }); + Ui::AddSkip(chargeInner); - const auto exceptions = Settings::AddButtonWithLabel( - chargeInner, - tr::lng_messages_privacy_remove_fee(), - std::move(label), - st::settingsButtonNoIcon); + state->stars = SetupChargeSlider( + chargeInner, + session->user(), + savedValue); - const auto shower = exceptions->lifetime().make_state(); - exceptions->setClickedCallback([=] { - *shower = session->api().userPrivacy().value( + Ui::AddSkip(chargeInner); + Ui::AddSubsectionTitle( + chargeInner, + tr::lng_messages_privacy_exceptions()); + + const auto key = Api::UserPrivacy::Key::NoPaidMessages; + session->api().userPrivacy().reload(key); + auto label = session->api().userPrivacy().value( key - ) | rpl::take( - 1 - ) | rpl::start_with_next([=](const Api::UserPrivacy::Rule &value) { - EditNoPaidMessagesExceptions(controller, value); + ) | rpl::map([=](const Api::UserPrivacy::Rule &value) { + using namespace Settings; + const auto always = ExceptionUsersCount(value.always.peers); + return always + ? tr::lng_edit_privacy_exceptions_count( + tr::now, + lt_count, + always) + : QString(); }); - }); - Ui::AddSkip(chargeInner); - Ui::AddDividerText(chargeInner, tr::lng_messages_privacy_remove_about()); - using namespace rpl::mappers; - chargeWrap->toggleOn(group->value() | rpl::map(_1 == kOptionCharge)); - chargeWrap->finishAnimating(); + const auto exceptions = Settings::AddButtonWithLabel( + chargeInner, + tr::lng_messages_privacy_remove_fee(), + std::move(label), + st::settingsButtonNoIcon); + const auto shower = exceptions->lifetime().make_state(); + exceptions->setClickedCallback([=] { + *shower = session->api().userPrivacy().value( + key + ) | rpl::take( + 1 + ) | rpl::start_with_next([=](const Api::UserPrivacy::Rule &value) { + EditNoPaidMessagesExceptions(controller, value); + }); + }); + Ui::AddSkip(chargeInner); + Ui::AddDividerText( + chargeInner, + tr::lng_messages_privacy_remove_about()); + + using namespace rpl::mappers; + chargeWrap->toggleOn(group->value() | rpl::map(_1 == kOptionCharge)); + chargeWrap->finishAnimating(); + } using WeakToast = base::weak_ptr; const auto toast = std::make_shared(); const auto showToast = [=] { @@ -1108,7 +1114,9 @@ void EditMessagesPrivacyBox( if (!allowed()) { CreateRadiobuttonLock(restricted, st::messagePrivacyCheck); - CreateRadiobuttonLock(charged, st::messagePrivacyCheck); + if (charged) { + CreateRadiobuttonLock(charged, st::messagePrivacyCheck); + } group->setChangedCallback([=](int value) { if (value == kOptionPremium || value == kOptionCharge) { @@ -1170,19 +1178,20 @@ rpl::producer SetupChargeSlider( : tr::lng_messages_privacy_price()); auto values = std::vector(); + const auto maxStars = peer->session().appConfig().paidMessageStarsMax(); if (chargeStars < kStarsMin) { values.push_back(chargeStars); } - for (auto i = kStarsMin; i < 100; ++i) { + for (auto i = kStarsMin; i < std::min(100, maxStars); ++i) { values.push_back(i); } - for (auto i = 100; i < 1000; i += 10) { + for (auto i = 100; i < std::min(1000, maxStars); i += 10) { if (i < chargeStars + 10 && chargeStars < i) { values.push_back(chargeStars); } values.push_back(i); } - for (auto i = 1000; i < kStarsMax + 1; i += 100) { + for (auto i = 1000; i < maxStars + 1; i += 100) { if (i < chargeStars + 100 && chargeStars < i) { values.push_back(chargeStars); } @@ -1200,6 +1209,7 @@ rpl::producer SetupChargeSlider( valuesCount, [=](int index) { return values[index]; }, chargeStars, + maxStars, setStars, setStars), st::boxRowPadding); @@ -1208,19 +1218,18 @@ rpl::producer SetupChargeSlider( Ui::AddSkip(container, skip); auto dollars = state->stars.value() | rpl::map([=](int stars) { - const auto ratio = peer->session().appConfig().get( - u"stars_usd_withdraw_rate_x1000"_q, - 1200); - const auto dollars = int(base::SafeRound(stars * (ratio / 1000.))); + const auto ratio = peer->session().appConfig().starsWithdrawRate(); + const auto dollars = int(base::SafeRound(stars * ratio)); return '~' + Ui::FillAmountAndCurrency(dollars, u"USD"_q); }); + const auto percent = peer->session().appConfig().paidMessageCommission(); Ui::AddDividerText( container, (group ? tr::lng_rights_charge_price_about : tr::lng_messages_privacy_price_about)( lt_percent, - rpl::single(QString::number(kGetPercent) + '%'), + rpl::single(QString::number(percent / 10.) + '%'), lt_amount, std::move(dollars))); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index ecb0b8e76..486383f0b 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -1160,34 +1160,39 @@ void ShowEditPeerPermissionsBox( rpl::variable starsPerMessage; }; const auto state = inner->lifetime().make_state(); + const auto channel = peer->asChannel(); + const auto available = channel && channel->paidMessagesAvailable(); Ui::AddSkip(inner); Ui::AddDivider(inner); - Ui::AddSkip(inner); - const auto starsPerMessage = peer->isChannel() - ? peer->asChannel()->starsPerMessage() - : 0; - const auto charging = inner->add(object_ptr( - inner, - tr::lng_rights_charge_stars(), - st::settingsButtonNoIcon)); - charging->toggleOn(rpl::single(starsPerMessage > 0)); - Ui::AddSkip(inner); - Ui::AddDividerText(inner, tr::lng_rights_charge_stars_about()); - - const auto chargeWrap = inner->add( - object_ptr>( + auto charging = (Ui::SettingsButton*)nullptr; + if (available) { + Ui::AddSkip(inner); + const auto starsPerMessage = peer->isChannel() + ? peer->asChannel()->starsPerMessage() + : 0; + charging = inner->add(object_ptr( inner, - object_ptr(inner))); - chargeWrap->toggleOn(charging->toggledValue()); - chargeWrap->finishAnimating(); - const auto chargeInner = chargeWrap->entity(); + tr::lng_rights_charge_stars(), + st::settingsButtonNoIcon)); + charging->toggleOn(rpl::single(starsPerMessage > 0)); + Ui::AddSkip(inner); + Ui::AddDividerText(inner, tr::lng_rights_charge_stars_about()); - Ui::AddSkip(chargeInner); - state->starsPerMessage = SetupChargeSlider( - chargeInner, - peer, - starsPerMessage); + const auto chargeWrap = inner->add( + object_ptr>( + inner, + object_ptr(inner))); + chargeWrap->toggleOn(charging->toggledValue()); + chargeWrap->finishAnimating(); + const auto chargeInner = chargeWrap->entity(); + + Ui::AddSkip(chargeInner); + state->starsPerMessage = SetupChargeSlider( + chargeInner, + peer, + starsPerMessage); + } static constexpr auto kSendRestrictions = Flag::EmbedLinks | Flag::SendGames @@ -1242,7 +1247,7 @@ void ShowEditPeerPermissionsBox( const auto boostsUnrestrict = hasRestrictions ? state->boostsUnrestrict.current() : 0; - const auto starsPerMessage = charging->toggled() + const auto starsPerMessage = (charging && charging->toggled()) ? state->starsPerMessage.current() : 0; done({ diff --git a/Telegram/SourceFiles/data/components/credits.cpp b/Telegram/SourceFiles/data/components/credits.cpp index cea4020d8..a3e57c0d2 100644 --- a/Telegram/SourceFiles/data/components/credits.cpp +++ b/Telegram/SourceFiles/data/components/credits.cpp @@ -37,10 +37,7 @@ void Credits::apply(const MTPDupdateStarsBalance &data) { rpl::producer Credits::rateValue( not_null ownedBotOrChannel) { - return rpl::single( - _session->appConfig().get( - u"stars_usd_withdraw_rate_x1000"_q, - 1200) / 1000.); + return rpl::single(_session->appConfig().starsWithdrawRate()); } void Credits::load(bool force) { diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index f02f31d3d..f21769328 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -1167,7 +1167,8 @@ void ApplyChannelUpdate( | Flag::CanViewRevenue | Flag::PaidMediaAllowed | Flag::CanViewCreditsRevenue - | Flag::StargiftsAvailable; + | Flag::StargiftsAvailable + | Flag::PaidMessagesAvailable; channel->setFlags((channel->flags() & ~mask) | (update.is_can_set_username() ? Flag::CanSetUsername : Flag()) | (update.is_can_view_participants() @@ -1191,6 +1192,9 @@ void ApplyChannelUpdate( : Flag()) | (update.is_stargifts_available() ? Flag::StargiftsAvailable + : Flag()) + | (update.is_paid_messages_available() + ? Flag::PaidMessagesAvailable : Flag())); channel->setUserpicPhoto(update.vchat_photo()); if (const auto migratedFrom = update.vmigrated_from_chat_id()) { diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index d9e835320..115516073 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -72,6 +72,7 @@ enum class ChannelDataFlag : uint64 { CanViewCreditsRevenue = (1ULL << 34), SignatureProfiles = (1ULL << 35), StargiftsAvailable = (1ULL << 36), + PaidMessagesAvailable = (1ULL << 37), }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; using ChannelDataFlags = base::flags; @@ -262,6 +263,9 @@ public: [[nodiscard]] bool stargiftsAvailable() const { return flags() & Flag::StargiftsAvailable; } + [[nodiscard]] bool paidMessagesAvailable() const { + return flags() & Flag::PaidMessagesAvailable; + } [[nodiscard]] static ChatRestrictionsInfo KickedRestrictedRights( not_null participant); diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index c26d57126..d1f10b7e8 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -73,6 +73,22 @@ int AppConfig::starrefCommissionMax() const { return get(u"starref_max_commission_permille"_q, 900); } +float64 AppConfig::starsWithdrawRate() const { + return get(u"stars_usd_withdraw_rate_x1000"_q, 1300) / 1000.; +} + +bool AppConfig::paidMessagesAvailable() const { + return get(u"stars_paid_messages_available"_q, false); +} + +int AppConfig::paidMessageStarsMax() const { + return get(u"stars_paid_message_amount_max"_q, 10'000); +} + +int AppConfig::paidMessageCommission() const { + return get(u"stars_paid_message_commission_permille"_q, 850); +} + void AppConfig::refresh(bool force) { if (_requestId || !_api) { if (force) { diff --git a/Telegram/SourceFiles/main/main_app_config.h b/Telegram/SourceFiles/main/main_app_config.h index 3092aa567..58a7da4de 100644 --- a/Telegram/SourceFiles/main/main_app_config.h +++ b/Telegram/SourceFiles/main/main_app_config.h @@ -72,6 +72,11 @@ public: [[nodiscard]] int starrefCommissionMin() const; [[nodiscard]] int starrefCommissionMax() const; + [[nodiscard]] float64 starsWithdrawRate() const; + [[nodiscard]] bool paidMessagesAvailable() const; + [[nodiscard]] int paidMessageStarsMax() const; + [[nodiscard]] int paidMessageCommission() const; + void refresh(bool force = false); private: From 4121c99f3652d128e8dc7a4d483db99881a64f17 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 3 Mar 2025 11:42:10 +0400 Subject: [PATCH 070/103] Allow folders submenu to have a scroll. --- Telegram/SourceFiles/boxes/boxes.style | 7 +++++++ Telegram/SourceFiles/boxes/choose_filter_box.cpp | 2 +- Telegram/SourceFiles/dialogs/dialogs.style | 4 ---- Telegram/SourceFiles/window/window_peer_menu.cpp | 1 + Telegram/lib_ui | 2 +- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 90e59cd78..fc3421394 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -1127,3 +1127,10 @@ profileQrBackgroundRadius: 12px; profileQrIcon: icon{{ "qr_mini", windowActiveTextFg }}; profileQrBackgroundMargins: margins(36px, 12px, 36px, 12px); profileQrBackgroundPadding: margins(0px, 24px, 0px, 24px); + +foldersMenu: PopupMenu(popupMenuWithIcons) { + maxHeight: 320px; + menu: Menu(menuWithIcons) { + itemPadding: margins(54px, 8px, 44px, 8px); + } +} diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp index 685ef547f..7448332d3 100644 --- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/choose_filter_box.cpp @@ -290,7 +290,7 @@ void FillChooseFilterMenu( const auto title = filter.title(); auto item = base::make_unique_q( menu.get(), - st::foldersMenu, + menu->st().menu, Ui::Menu::CreateAction( menu.get(), Ui::Text::FixAmpersandInAction(title.text.text), diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index f2ff4723c..4027da23e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -789,7 +789,3 @@ dialogsPopularAppsPadding: margins(10px, 8px, 10px, 12px); dialogsPopularAppsAbout: FlatLabel(boxDividerLabel) { minWidth: 128px; } - -foldersMenu: Menu(menuWithIcons) { - itemPadding: margins(54px, 8px, 44px, 8px); -} diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 634c3c6f9..5e131bce8 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -633,6 +633,7 @@ void Filler::addToggleFolder() { .fillSubmenu = [&](not_null menu) { FillChooseFilterMenu(controller, menu, history); }, + .submenuSt = &st::foldersMenu, }); } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index cfecbd3df..da0d634ec 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit cfecbd3df4c1d0bfba3d708dfb49387c8ae2127b +Subproject commit da0d634ec373d8ba0b8f66f3381a87e43edffdf2 From d1e6150874ad9a59fe8c54d32b8e3324a99f1701 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 3 Mar 2025 13:03:20 +0400 Subject: [PATCH 071/103] Don't suggest userpics to paid-restricted. --- Telegram/SourceFiles/boxes/background_preview_box.cpp | 4 +++- Telegram/SourceFiles/ui/controls/userpic_button.cpp | 1 + Telegram/SourceFiles/window/window_peer_menu.cpp | 3 +-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index 828b2d095..0faa67772 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -1078,7 +1078,9 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector &bg) { ? tr::lng_background_other_group(tr::now) : forChannel() ? tr::lng_background_other_channel(tr::now) - : (_forPeer && !_fromMessageId) + : (_forPeer + && !_fromMessageId + && !_forPeer->starsPerMessageChecked()) ? tr::lng_background_other_info( tr::now, lt_user, diff --git a/Telegram/SourceFiles/ui/controls/userpic_button.cpp b/Telegram/SourceFiles/ui/controls/userpic_button.cpp index c1d2d2187..dec06a911 100644 --- a/Telegram/SourceFiles/ui/controls/userpic_button.cpp +++ b/Telegram/SourceFiles/ui/controls/userpic_button.cpp @@ -247,6 +247,7 @@ bool UserpicButton::canSuggestPhoto(not_null user) const { // Server allows suggesting photos only in non-empty chats. return !user->isSelf() && !user->isBot() + && !user->starsPerMessageChecked() && (user->owner().history(user)->lastServerMessage() != nullptr); } diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 5e131bce8..b4c1daf62 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1203,8 +1203,7 @@ void Filler::addThemeEdit() { if (!user || user->isInaccessible()) { return; } - if ((user->requiresPremiumToWrite() && !user->session().premium()) - || user->starsPerMessage() > 0) { + if (user->requiresPremiumToWrite() && !user->session().premium()) { return; } const auto controller = _controller; From 4ab4eb8ef2334d19f448735398873b2cc95603e8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 3 Mar 2025 13:36:14 +0400 Subject: [PATCH 072/103] Pause voice in pay-to-send chats. --- Telegram/SourceFiles/history/history_widget.cpp | 12 ++++++++---- .../view/controls/history_view_compose_controls.cpp | 3 +++ .../view/controls/history_view_voice_record_bar.cpp | 9 +++++++++ .../view/controls/history_view_voice_record_bar.h | 2 ++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index c7c55f463..459b3951c 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -5169,12 +5169,13 @@ void HistoryWidget::updateSendButtonType() { : 0; }(); const auto perMessage = _peer ? _peer->starsPerMessageChecked() : 0; - const auto stars = perMessage - ? perMessage * ComputeSendingMessagesCount(_history, { + const auto messages = _voiceRecordBar->isListenState() + ? 1 + : ComputeSendingMessagesCount(_history, { .forward = &_forwardPanel->items(), .text = &_field->getTextWithTags(), - }) - : 0; + }); + const auto stars = perMessage ? (perMessage * messages) : 0; _send->setState({ .type = (delay > 0) ? Type::Slowmode : type, .slowmodeDelay = delay, @@ -5775,6 +5776,9 @@ void HistoryWidget::fieldFocused() { } void HistoryWidget::updateFieldPlaceholder() { + _voiceRecordBar->setPauseInsteadSend(_history + && _history->peer->starsPerMessageChecked() > 0); + if (!_editMsgId && _inlineBot && !_inlineLookingUpBot) { _field->setPlaceholder( rpl::single(_inlineBot->botInfo->inlinePlaceholder.mid(1)), diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 1fee75916..0d277b016 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1714,6 +1714,9 @@ void ComposeControls::initFieldAutocomplete() { } void ComposeControls::updateFieldPlaceholder() { + _voiceRecordBar->setPauseInsteadSend(_history + && _history->peer->starsPerMessageChecked() > 0); + if (!isEditingMessage() && _isInlineBot) { _field->setPlaceholder( rpl::single(_inlineBot->botInfo->inlinePlaceholder.mid(1)), diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 696a480b1..bcf5cf292 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -1768,6 +1768,10 @@ void VoiceRecordBar::setTTLFilter(FilterCallback &&callback) { _hasTTLFilter = std::move(callback); } +void VoiceRecordBar::setPauseInsteadSend(bool pauseInsteadSend) { + _pauseInsteadSend = pauseInsteadSend; +} + void VoiceRecordBar::initLockGeometry() { const auto parent = static_cast(parentWidget()); rpl::merge( @@ -1918,6 +1922,11 @@ void VoiceRecordBar::recordUpdated(quint16 level, int samples) { void VoiceRecordBar::stop(bool send) { if (isHidden() && !send) { return; + } else if (send && _pauseInsteadSend) { + _fullRecord = true; + stopRecording(StopType::Listen); + _lockShowing = false; + return; } const auto ttlBeforeHide = peekTTLState(); auto disappearanceCallback = [=] { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index 027366a59..c1a9ff4a7 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -100,6 +100,7 @@ public: void setStartRecordingFilter(FilterCallback &&callback); void setTTLFilter(FilterCallback &&callback); + void setPauseInsteadSend(bool pauseInsteadSend); [[nodiscard]] bool isRecording() const; [[nodiscard]] bool isRecordingLocked() const; @@ -193,6 +194,7 @@ private: FilterCallback _hasTTLFilter; bool _warningShown = false; + bool _pauseInsteadSend = false; rpl::variable _recording = false; rpl::variable _inField = false; From 8e83a55143a30ac58c0abc1d37ca69af3e524a95 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 3 Mar 2025 12:42:17 +0300 Subject: [PATCH 073/103] Added ability to request earn stats without currency earn in megagroups. --- .../earn/info_channel_earn_list.cpp | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp index e556b7c58..302975afe 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp @@ -297,9 +297,7 @@ void InnerWidget::load() { _showFinished.events( ) | rpl::take(1) | rpl::start_with_next([=] { - state->api.request( - ) | rpl::start_with_error_done(fail, [=] { - _state.currencyEarn = state->api.data(); + const auto nextRequests = [=] { state->apiCreditsHistory.request({}, [=]( const Data::CreditsStatusSlice &data) { _state.creditsStatusSlice = data; @@ -322,6 +320,19 @@ void InnerWidget::load() { state->apiPremiumBotLifetime.destroy(); }, state->apiPremiumBotLifetime); }); + }; + const auto isMegagroup = _peer->isMegagroup(); + state->api.request( + ) | rpl::start_with_error_done([=](const QString &error) { + if (isMegagroup) { + _state.currencyEarn = {}; + nextRequests(); + } else { + show->showToast(error); + } + }, [=] { + _state.currencyEarn = state->api.data(); + nextRequests(); }, state->apiLifetime); }, lifetime()); } @@ -333,7 +344,8 @@ void InnerWidget::fill() { : nullptr; const auto channel = _peer->asChannel(); const auto canViewCurrencyEarn = channel - ? (channel->flags() & ChannelDataFlag::CanViewRevenue) + ? ((channel->flags() & ChannelDataFlag::CanViewRevenue) + && !channel->isMegagroup()) : true; const auto &data = canViewCurrencyEarn ? _state.currencyEarn From 0605c7b2bce175be8e924406e396606b6ed07448 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 3 Mar 2025 13:33:12 +0300 Subject: [PATCH 074/103] Added ability to display possible currency earn in megagroups in future. --- .../earn/info_channel_earn_list.cpp | 26 ++++++++++++++----- .../earn/info_channel_earn_widget.h | 1 + 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp index 302975afe..bac05ef0c 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp @@ -326,6 +326,9 @@ void InnerWidget::load() { ) | rpl::start_with_error_done([=](const QString &error) { if (isMegagroup) { _state.currencyEarn = {}; + if (error == u"BROADCAST_REQUIRED"_q) { + _state.canViewCurrencyMegagroupEarn = false; + } nextRequests(); } else { show->showToast(error); @@ -343,10 +346,17 @@ void InnerWidget::fill() { ? _peer->asUser() : nullptr; const auto channel = _peer->asChannel(); - const auto canViewCurrencyEarn = channel - ? ((channel->flags() & ChannelDataFlag::CanViewRevenue) - && !channel->isMegagroup()) - : true; + const auto canViewCurrencyEarn = [&] { + if (!channel) { + return true; + } else if (!(channel->flags() & ChannelDataFlag::CanViewRevenue)) { + return false; + } else if (channel->isMegagroup()) { + return _state.canViewCurrencyMegagroupEarn; + } else { + return true; + } + }(); const auto &data = canViewCurrencyEarn ? _state.currencyEarn : Data::EarnStatistics(); @@ -625,9 +635,11 @@ void InnerWidget::fill() { st::defaultBoxDividerLabelPadding, RectPart::Top | RectPart::Bottom)); }; - addAboutWithLearn(bot - ? tr::lng_channel_earn_about_bot - : tr::lng_channel_earn_about); + if (canViewCurrencyEarn) { + addAboutWithLearn(bot + ? tr::lng_channel_earn_about_bot + : tr::lng_channel_earn_about); + } { using Type = Statistic::ChartViewType; Ui::AddSkip(container); diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_widget.h b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_widget.h index becd834c6..031b404d9 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_widget.h +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_widget.h @@ -34,6 +34,7 @@ public: Data::CreditsEarnStatistics creditsEarn; Data::CreditsStatusSlice creditsStatusSlice; PeerId premiumBotId = PeerId(0); + bool canViewCurrencyMegagroupEarn = true; }; void setState(SavedState states); From bd70a05861467638db6cd938aedc47d02afe1ecb Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 3 Mar 2025 13:34:37 +0300 Subject: [PATCH 075/103] Removed button to turn off sponsored messages in megagroups. --- .../earn/info_channel_earn_list.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp index bac05ef0c..f9dc38da9 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp @@ -1001,7 +1001,8 @@ void InnerWidget::fill() { const auto sectionIndex = container->lifetime().make_state(0); const auto rebuildLists = [=]( const Memento::SavedState &data, - not_null listsContainer) { + not_null listsContainer, + not_null historyDividerContainer) { const auto hasCurrencyTab = !data.currencyEarn.firstHistorySlice.list.empty(); const auto hasCreditsTab = !data.creditsStatusSlice.list.empty(); @@ -1385,9 +1386,9 @@ void InnerWidget::fill() { true); } if (hasCurrencyTab || hasCreditsTab) { - Ui::AddSkip(listsContainer); - Ui::AddDivider(listsContainer); - Ui::AddSkip(listsContainer); + Ui::AddSkip(historyDividerContainer); + Ui::AddDivider(historyDividerContainer); + Ui::AddSkip(historyDividerContainer); } listsContainer->resizeToWidth(width()); @@ -1395,18 +1396,20 @@ void InnerWidget::fill() { const auto historyContainer = container->add( object_ptr(container)); + const auto historyDividerContainer = container->add( + object_ptr(container)); rpl::single(rpl::empty) | rpl::then( _stateUpdated.events() ) | rpl::start_with_next([=] { const auto listsContainer = historyContainer->add( object_ptr(container)); - rebuildLists(_state, listsContainer); + rebuildLists(_state, listsContainer, historyDividerContainer); while (historyContainer->count() > 1) { delete historyContainer->widgetAt(0); } }, historyContainer->lifetime()); - if (channel) { + if (channel && !channel->isMegagroup()) { //constexpr auto kMaxCPM = 50; // Debug. const auto requiredLevel = Data::LevelLimits(session) .channelRestrictSponsoredLevelMin(); @@ -1463,6 +1466,10 @@ void InnerWidget::fill() { Ui::AddSkip(container); Ui::AddDividerText(container, tr::lng_channel_earn_off_about()); + } else { + while (historyDividerContainer->count() > 1) { + delete historyDividerContainer->widgetAt(0); + } } Ui::AddSkip(container); From 7d2878d81cc0eadd83c004f969e2583635aa0b2d Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 4 Mar 2025 14:13:39 +0400 Subject: [PATCH 076/103] Support gifting premium for stars. --- .../icons/payments/premium_emoji.png | Bin 0 -> 370 bytes .../icons/payments/premium_emoji@2x.png | Bin 0 -> 712 bytes .../icons/payments/premium_emoji@3x.png | Bin 0 -> 926 bytes Telegram/Resources/langs/lang.strings | 4 + Telegram/SourceFiles/api/api_premium.cpp | 3 + Telegram/SourceFiles/boxes/boxes.style | 5 - .../SourceFiles/boxes/send_credits_box.cpp | 4 +- Telegram/SourceFiles/boxes/send_credits_box.h | 2 +- Telegram/SourceFiles/boxes/star_gift_box.cpp | 110 +++++++++++++++--- .../chat_helpers/message_field.cpp | 3 +- Telegram/SourceFiles/core/shortcuts.cpp | 8 ++ .../data/stickers/data_custom_emoji.cpp | 9 ++ .../data/stickers/data_custom_emoji.h | 1 + .../peer_gifts/info_peer_gifts_common.cpp | 52 +++++++-- .../info/peer_gifts/info_peer_gifts_common.h | 3 + .../payments/payments_checkout_process.cpp | 29 ++--- .../payments/payments_checkout_process.h | 3 +- .../SourceFiles/payments/payments_form.cpp | 35 ++++-- Telegram/SourceFiles/payments/payments_form.h | 2 + .../payments/payments_non_panel_process.cpp | 5 +- .../SourceFiles/settings/settings_credits.cpp | 106 ++++++++++------- .../SourceFiles/settings/settings_credits.h | 23 +++- .../settings/settings_credits_graphics.cpp | 4 +- .../SourceFiles/ui/controls/send_button.cpp | 3 +- Telegram/SourceFiles/ui/effects/credits.style | 17 ++- Telegram/lib_ui | 2 +- 26 files changed, 321 insertions(+), 112 deletions(-) create mode 100644 Telegram/Resources/icons/payments/premium_emoji.png create mode 100644 Telegram/Resources/icons/payments/premium_emoji@2x.png create mode 100644 Telegram/Resources/icons/payments/premium_emoji@3x.png diff --git a/Telegram/Resources/icons/payments/premium_emoji.png b/Telegram/Resources/icons/payments/premium_emoji.png new file mode 100644 index 0000000000000000000000000000000000000000..e8b8fcf292b6f466acfdcc9d938efbbb44ff8551 GIT binary patch literal 370 zcmV-&0ge8NP))s#DvaIX6RaL$AMhLA|tBEh#ZnqfY&t)KlBl9;pUDvPIE5k6KjYLs=9%^*9ZBNnw z07X$I0-B~hOi>iB>w2EoG|gw=Dc|>#Byk*PoU>Rgj>luuG^f*Pa`G(w1N8;rTRq58 Q#Q*>R07*qoM6N<$f-be3$^ZZW literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/payments/premium_emoji@2x.png b/Telegram/Resources/icons/payments/premium_emoji@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8824f11b97c6f1051e2e8f9d9e97022a6658631b GIT binary patch literal 712 zcmV;(0yq7MP)CmOpFiP#DHfOah{{S_i3CB@Tj?B06>IP#m=AQk(># zNL#SD2~t--fKqVr&n!q6`xQh)aVW*5D%v3g3=vz3MzJlPJ9sO_s7=}|m-`HX9NzPL zp7Wl=NdWkdWV6{ioldLOGMmlf(@_+Ce0+ouLI_KxlK2K^XJ_z>oKB~hbR5T@o}RuM zR4NrQ4X&=PAV)5jOB}jXDy1ljYf!7z#4(6OB3$}h9*^g@bcI5}Fw8oG`}=zw$2SvD z?huAyMx)Vgw;vxLCzDBw#jfN5|(9Wv)NJ&0sv5})t8r-R;!gD z2!bH=dOeD+F#c8$LYk&Yk|as8-EODTX^Nr%0H9nhuWQ0@^ziTi0FVFx4h{}}koQ}u zR4OpvgSWRg(GK;eK0ZD!E-sqQ=HGXp&o>+n#cIxUIz2r-UG}tEt&8hyrC642G#d4K zeKUPL9=qM{1s3`0f#Y~E81(!7YPEVgop!t3ZnxX%bmq@qucy&y;_-MW6k6Ar(P-rL zdVg}=?(Xg;5($=NH)MZ)em*=r6pYsCbT>CQ~_20)TvZzGMQ{f&y~yN zRzk3K^plg5d_KRkv%@ouTrL-oeSd$SXYhrp)v5r4+uK_~bO<3$(|qX&g1o-I3N?Tb z%49MwO~M^A7!2Fn+XB}6DS{w)8La#584L!=WHOu0P9~F;1tyb;R}k(ezTfZ1VzI5Q zt$D%T-d-k?d4GRjDhr3hybJ*pP%zGz%VS4NYv}~ uXf*om>2^3AcXxOFe!o~OuH58*Uw;6mxjm>12Dz;O00008UrDyfJmQehMY&P;mWVMXR_&TVaceEkoy z!`}AWp7Y@V`0f8|dV2cq?vAEuhr^+^1%RQUA^6ej>uYOktC|`d$7z~|8SU=wstFn! z8-tu|Hk&#ctyX(;bHfGg?d_=pnwXg2YS=nEJC)Yxbh`Wd`|m*e`}<0RW@cu-)3Dj? zcI7mBz5eOxi63-ybfg?;Zf=fW!`9Q&qlAVah(sbG1ay3Sj3CI5gZzHK5DnYN$jDDQ z5Ck!qOfHv8xCTP_^71l1K3-Z{TEL08CngBOVzK=Bn#E!%E-n^*J&8nurfHg{Baukv zMkEqRCX+b^kR-XhynKFs&M-_t_dt5Lx3|Gyu)V$gQ)4g~9v&VPc4U=GrK+l`001Ze zps%m5q@+ajbjT8hVO?Ea?;wU@)YY)XVzKuvolbXlcBbx>1p)ySML%@}L4-mfwKQvM zYdDT)-!Tlsd_JG@nw6CmjYjkBL=FxP3S1dvX=!Qc%S`30uC9)vD4wIdy}d=F(Vd;0 z#l^*`sj2?{eyi0=5X8yJNkJN)&&OTtaxZ=wjmB=bI~)#@BvBNN$K#immlqco?B~_h zRn~)PwOZY7cXxMpo@>p>&XpN1H>+9=<&Xn8jmY>qx+#Co53Z*~@old9B zl$Ms3U@$0x0wEk79hH;P*4DPQwIz-MAquOt8-k4LZ9OTDa*Dk>`YcgO=W8jS-319EAA zbWle}hkO_Sq=V|~>*d1$ARSa)T`eC50A)a_R4Nn-Q4}Q$h%XGo(&;qMGn`B&{eFL0 zSsA;3Q&ZFS_IBPUU#r!M1l82k@K6>O7R+Wdca+U$J2*Jt0g)sr5>#7T%RL;!F!S^C zCXFH@skofNSL^j^j?JGaL>-KR>Uot`_v-;kSRHzp#(07FGK~82|tP07*qoM6N<$f@~_S AxBvhE literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 294adcadb..250ba9584 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3329,6 +3329,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_premium_about" = "Give {name} access to exclusive features with Telegram Premium. {features}"; "lng_gift_premium_features" = "See Features >"; "lng_gift_premium_label" = "Premium"; +"lng_gift_premium_by_stars" = "or {amount}"; "lng_gift_stars_subtitle" = "Gift Stars"; "lng_gift_stars_about" = "Give {name} gifts that can be kept on your profile or converted to Stars. {link}"; "lng_gift_stars_link" = "What are Stars >"; @@ -3340,6 +3341,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_send_title" = "Send a Gift"; "lng_gift_send_message" = "Enter Message"; "lng_gift_send_anonymous" = "Hide My Name"; +"lng_gift_send_pay_with_stars" = "Pay with {amount}"; +"lng_gift_send_stars_balance" = "Your balance is {amount}. {link}"; +"lng_gift_send_stars_balance_link" = "Get More Stars >"; "lng_gift_send_anonymous_self" = "Hide my name and message from visitors to my profile."; "lng_gift_send_anonymous_about" = "You can hide your name and message from visitors to {user}'s profile. {recipient} will still see your name and message."; "lng_gift_send_anonymous_about_paid" = "You can hide your name from visitors to {user}'s profile. {recipient} will still see your name."; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index fe3004cda..daa9d83cb 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -479,6 +479,9 @@ rpl::producer PremiumGiftCodeOptions::request() { for (const auto &tlOption : result.v) { const auto &data = tlOption.data(); tlMapOptions[data.vusers().v].push_back(tlOption); + if (qs(data.vcurrency()) == Ui::kCreditsCurrency) { + continue; + } const auto token = Token{ data.vusers().v, data.vmonths().v }; _stores[token] = Store{ diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index fc3421394..52fb1b43b 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -30,11 +30,6 @@ ShortInfoBox { labeledOneLine: FlatLabel; } -boxStarIconEmoji: IconEmoji { - icon: icon{{ "payments/small_star", windowFg }}; - padding: margins(0px, -2px, 0px, 0px); -} - countryRowHeight: 36px; countryRowNameFont: semiboldFont; countryRowNameFg: boxTextFg; diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index d1e74eeed..8091e7ea4 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -512,7 +512,7 @@ TextWithEntities CreditsEmoji(not_null session) { TextWithEntities CreditsEmojiSmall(not_null session) { return Ui::Text::IconEmoji( - &st::boxStarIconEmoji, + &st::starIconEmoji, QString(QChar(0x2B50))); } @@ -570,7 +570,7 @@ not_null SetButtonMarkedLabel( }, st, textFg); } -void SendStarGift( +void SendStarsForm( not_null session, std::shared_ptr data, Fn)> done) { diff --git a/Telegram/SourceFiles/boxes/send_credits_box.h b/Telegram/SourceFiles/boxes/send_credits_box.h index cc84b379e..e51dc39b3 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.h +++ b/Telegram/SourceFiles/boxes/send_credits_box.h @@ -52,7 +52,7 @@ not_null SetButtonMarkedLabel( const style::FlatLabel &st, const style::color *textFg = nullptr); -void SendStarGift( +void SendStarsForm( not_null session, std::shared_ptr data, Fn)> done); diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index fcd18526b..89c53f604 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -128,6 +128,7 @@ struct GiftDetails { uint64 randomId = 0; bool anonymous = false; bool upgraded = false; + bool byStars = false; }; class PreviewDelegate final : public DefaultElementDelegate { @@ -497,7 +498,14 @@ void PreviewWrap::prepare(rpl::producer details) { std::move(details) | rpl::start_with_next([=](GiftDetails details) { const auto &descriptor = details.descriptor; const auto cost = v::match(descriptor, [&](GiftTypePremium data) { - return FillAmountAndCurrency(data.cost, data.currency, true); + const auto stars = (details.byStars && data.stars) + ? data.stars + : (data.currency == kCreditsCurrency) + ? data.cost + : 0; + return stars + ? tr::lng_gift_stars_title(tr::now, lt_count, stars) + : FillAmountAndCurrency(data.cost, data.currency, true); }, [&](GiftTypeStars data) { const auto stars = data.info.stars + (details.upgraded ? data.info.starsToUpgrade : 0); @@ -1118,16 +1126,35 @@ void SendGift( std::shared_ptr api, const GiftDetails &details, Fn done) { + const auto processNonPanelPaymentFormFactory + = Payments::ProcessNonPanelPaymentFormFactory(window, done); v::match(details.descriptor, [&](const GiftTypePremium &gift) { - auto invoice = api->invoice(1, gift.months); - invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{ - .users = { peer->asUser() }, - .message = details.text, - }; - Payments::CheckoutProcess::Start(std::move(invoice), done); + if (details.byStars && gift.stars) { + auto invoice = Payments::InvoicePremiumGiftCode{ + .purpose = Payments::InvoicePremiumGiftCodeUsers{ + .users = { peer->asUser() }, + .message = details.text, + }, + .currency = Ui::kCreditsCurrency, + .randomId = details.randomId, + .amount = uint64(gift.stars), + .storeQuantity = 1, + .users = 1, + .months = gift.months, + }; + Payments::CheckoutProcess::Start( + std::move(invoice), + done, + processNonPanelPaymentFormFactory); + } else { + auto invoice = api->invoice(1, gift.months); + invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{ + .users = { peer->asUser() }, + .message = details.text, + }; + Payments::CheckoutProcess::Start(std::move(invoice), done); + } }, [&](const GiftTypeStars &gift) { - const auto processNonPanelPaymentFormFactory - = Payments::ProcessNonPanelPaymentFormFactory(window, done); Payments::CheckoutProcess::Start(Payments::InvoiceStarGift{ .giftId = gift.info.id, .randomId = details.randomId, @@ -1459,9 +1486,14 @@ void SendGiftBox( auto cost = state->details.value( ) | rpl::map([session](const GiftDetails &details) { return v::match(details.descriptor, [&](const GiftTypePremium &data) { - if (data.currency == kCreditsCurrency) { + const auto stars = (details.byStars && data.stars) + ? data.stars + : (data.currency == kCreditsCurrency) + ? data.cost + : 0; + if (stars) { return CreditsEmojiSmall(session).append( - Lang::FormatCountDecimal(std::abs(data.cost))); + Lang::FormatCountDecimal(std::abs(stars))); } return TextWithEntities{ FillAmountAndCurrency(data.cost, data.currency), @@ -1580,10 +1612,56 @@ void SendGiftBox( }, container->lifetime()); AddSkip(container); } - v::match(descriptor, [&](const GiftTypePremium &) { + v::match(descriptor, [&](const GiftTypePremium &data) { AddDividerText(messageInner, tr::lng_gift_send_premium_about( lt_user, rpl::single(peer->shortName()))); + + if (const auto byStars = data.stars) { + const auto star = Ui::Text::IconEmoji(&st::starIconEmojiColored); + AddSkip(container); + container->add( + object_ptr( + container, + tr::lng_gift_send_pay_with_stars( + lt_amount, + rpl::single(base::duplicate(star).append(Lang::FormatCountDecimal(byStars))), + Ui::Text::WithEntities), + st::settingsButtonNoIcon) + )->toggleOn(rpl::single(false))->toggledValue( + ) | rpl::start_with_next([=](bool toggled) { + auto now = state->details.current(); + now.byStars = toggled; + state->details = std::move(now); + }, container->lifetime()); + AddSkip(container); + + const auto balance = AddDividerText( + container, + tr::lng_gift_send_stars_balance( + lt_amount, + peer->session().credits().balanceValue( + ) | rpl::map([=](StarsAmount amount) { + return base::duplicate(star).append( + Lang::FormatStarsAmountDecimal(amount)); + }), + lt_link, + tr::lng_gift_send_stars_balance_link( + ) | Ui::Text::ToLink(), + Ui::Text::WithEntities)); + struct State { + Settings::BuyStarsHandler buyStars; + rpl::variable loading; + }; + const auto state = balance->lifetime().make_state(); + state->loading = state->buyStars.loadingValue(); + balance->setClickHandlerFilter([=](const auto &...) { + if (!state->loading.current()) { + state->buyStars.handler(window->uiShow())(); + } + return false; + }); + } }, [&](const GiftTypeStars &) { AddDividerText(container, peer->isSelf() ? tr::lng_gift_send_anonymous_self() @@ -1618,9 +1696,13 @@ void SendGiftBox( const auto weak = MakeWeak(box); const auto done = [=](Payments::CheckoutResult result) { if (result == Payments::CheckoutResult::Paid) { + if (details.byStars + || v::is(details.descriptor)) { + window->session().credits().load(true); + } const auto copy = state->media; window->showPeerHistory(peer); - ShowSentToast(window, descriptor, details); + ShowSentToast(window, details.descriptor, details); } if (const auto strong = weak.data()) { box->closeBox(); @@ -1853,6 +1935,8 @@ void GiftBox( box->setCustomCornersFilling(RectPart::FullTop); box->addButton(tr::lng_create_group_back(), [=] { box->closeBox(); }); + window->session().credits().load(); + FillBg(box); const auto &stUser = st::premiumGiftsUserpicButton; diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 617051762..927f1359c 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_boxes.h" #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" +#include "styles/style_credits.h" #include "styles/style_settings.h" #include "base/qt/qt_common_adapters.h" @@ -1282,7 +1283,7 @@ void SelectTextInFieldWithMargins( } TextWithEntities PaidSendButtonText(tr::now_t, int stars) { - return Ui::Text::IconEmoji(&st::boxStarIconEmoji).append( + return Ui::Text::IconEmoji(&st::starIconEmoji).append( Lang::FormatCountToShort(stars).string); } diff --git a/Telegram/SourceFiles/core/shortcuts.cpp b/Telegram/SourceFiles/core/shortcuts.cpp index 371ef6a6d..eaad16558 100644 --- a/Telegram/SourceFiles/core/shortcuts.cpp +++ b/Telegram/SourceFiles/core/shortcuts.cpp @@ -76,6 +76,14 @@ const auto CommandByName = base::flat_map{ { u"first_chat"_q , Command::ChatFirst }, { u"last_chat"_q , Command::ChatLast }, { u"self_chat"_q , Command::ChatSelf }, + { u"pinned_chat1"_q , Command::ChatPinned1 }, + { u"pinned_chat2"_q , Command::ChatPinned2 }, + { u"pinned_chat3"_q , Command::ChatPinned3 }, + { u"pinned_chat4"_q , Command::ChatPinned4 }, + { u"pinned_chat5"_q , Command::ChatPinned5 }, + { u"pinned_chat6"_q , Command::ChatPinned6 }, + { u"pinned_chat7"_q , Command::ChatPinned7 }, + { u"pinned_chat8"_q , Command::ChatPinned8 }, { u"previous_folder"_q , Command::FolderPrevious }, { u"next_folder"_q , Command::FolderNext }, diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index 695cb06ee..f541fda01 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" +#include "styles/style_credits.h" // giftBoxByStarsStyle namespace Data { namespace { @@ -1027,6 +1028,14 @@ TextWithEntities CustomEmojiManager::creditsEmoji(QMargins padding) { false)); } +TextWithEntities CustomEmojiManager::ministarEmoji(QMargins padding) { + return Ui::Text::SingleCustomEmoji( + registerInternalEmoji( + Ui::GenerateStars(st::giftBoxByStarsStyle.font->height, 1), + padding, + false)); +} + QString CustomEmojiManager::registerInternalEmoji( QImage emoji, QMargins padding, diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h index 60fd75f06..b01f7561e 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h @@ -100,6 +100,7 @@ public: [[nodiscard]] uint64 coloredSetId() const; [[nodiscard]] TextWithEntities creditsEmoji(QMargins padding = {}); + [[nodiscard]] TextWithEntities ministarEmoji(QMargins padding = {}); private: static constexpr auto kSizeCount = int(SizeTag::kCount); diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp index 798d2180c..98b521566 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp @@ -85,13 +85,11 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) { unsubscribe(); v::match(descriptor, [&](const GiftTypePremium &data) { const auto months = data.months; - const auto years = (months % 12) ? 0 : months / 12; _text = Ui::Text::String(st::giftBoxGiftHeight / 4); _text.setMarkedText( st::defaultTextStyle, - Ui::Text::Bold(years - ? tr::lng_years(tr::now, lt_count, years) - : tr::lng_months(tr::now, lt_count, months) + Ui::Text::Bold( + tr::lng_months(tr::now, lt_count, months) ).append('\n').append( tr::lng_gift_premium_label(tr::now) )); @@ -101,6 +99,18 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) { data.cost, data.currency, true)); + if (const auto stars = data.stars) { + const auto starsText = QString::number(stars); + _byStars.setMarkedText( + st::giftBoxByStarsStyle, + tr::lng_gift_premium_by_stars( + tr::now, + lt_amount, + _delegate->ministar().append(' ' + starsText), + Ui::Text::WithEntities), + kMarkupTextOptions, + _delegate->textContext()); + } _userpic = nullptr; if (!_stars) { _stars.emplace(this, true, starsType); @@ -170,7 +180,9 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) { QSize(buttonw, buttonh) ).marginsAdded(st::giftBoxButtonPadding); const auto skipy = _delegate->buttonSize().height() - - st::giftBoxButtonBottom + - (_byStars.isEmpty() + ? st::giftBoxButtonBottom + : st::giftBoxButtonBottomByStars) - inner.height(); const auto skipx = (width() - inner.width()) / 2; const auto outer = (width() - 2 * skipx); @@ -355,7 +367,9 @@ void GiftButton::paintEvent(QPaintEvent *e) { ? st::giftBoxSmallStickerTop : _text.isEmpty() ? st::giftBoxStickerStarTop - : st::giftBoxStickerTop), + : _byStars.isEmpty() + ? st::giftBoxStickerTop + : st::giftBoxStickerTopByStars), size.width(), size.height()), frame); @@ -367,7 +381,9 @@ void GiftButton::paintEvent(QPaintEvent *e) { ? st::giftBoxSmallStickerTop : _text.isEmpty() ? st::giftBoxStickerStarTop - : st::giftBoxStickerTop)); + : _byStars.isEmpty() + ? st::giftBoxStickerTop + : st::giftBoxStickerTopByStars)); _delegate->hiddenMark()->paint( p, frame, @@ -473,8 +489,9 @@ void GiftButton::paintEvent(QPaintEvent *e) { if (!_text.isEmpty()) { p.setPen(st::windowFg); _text.draw(p, { - .position = (position - + QPoint(0, st::giftBoxPremiumTextTop)), + .position = (position + QPoint(0, _byStars.isEmpty() + ? st::giftBoxPremiumTextTop + : st::giftBoxPremiumTextTopByStars)), .availableWidth = singlew, .align = style::al_top, }); @@ -492,6 +509,17 @@ void GiftButton::paintEvent(QPaintEvent *e) { + QPoint(padding.left(), padding.top())), .availableWidth = _price.maxWidth(), }); + + if (!_byStars.isEmpty()) { + p.setPen(st::creditsFg); + _byStars.draw(p, { + .position = QPoint( + position.x(), + _button.y() + _button.height() + st::giftBoxByStarsSkip), + .availableWidth = singlew, + .align = style::al_top, + }); + } } } @@ -515,6 +543,12 @@ TextWithEntities Delegate::star() { return owner->customEmojiManager().creditsEmoji(); } +TextWithEntities Delegate::ministar() { + const auto owner = &_window->session().data(); + const auto top = st::giftBoxByStarsStarTop; + return owner->customEmojiManager().ministarEmoji({ 0, top, 0, 0 }); +} + std::any Delegate::textContext() { return Core::MarkedTextContext{ .session = &_window->session(), diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h index 8362ef85d..d7d829dab 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h @@ -102,6 +102,7 @@ enum class GiftButtonMode { class GiftButtonDelegate { public: [[nodiscard]] virtual TextWithEntities star() = 0; + [[nodiscard]] virtual TextWithEntities ministar() = 0; [[nodiscard]] virtual std::any textContext() = 0; [[nodiscard]] virtual QSize buttonSize() = 0; [[nodiscard]] virtual QMargins buttonExtend() = 0; @@ -144,6 +145,7 @@ private: GiftDescriptor _descriptor; Ui::Text::String _text; Ui::Text::String _price; + Ui::Text::String _byStars; std::shared_ptr _userpic; QImage _uniqueBackgroundCache; std::unique_ptr _uniquePatternEmoji; @@ -170,6 +172,7 @@ public: ~Delegate(); TextWithEntities star() override; + TextWithEntities ministar() override; std::any textContext() override; QSize buttonSize() override; QMargins buttonExtend() override; diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index 0925d8937..379036e6e 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -66,7 +66,6 @@ void CheckoutProcess::Start( Mode mode, Fn reactivate, Fn nonPanelPaymentFormProcess) { - const auto hasNonPanelPaymentFormProcess = !!nonPanelPaymentFormProcess; auto &processes = LookupSessionProcesses(&item->history()->session()); const auto media = item->media(); const auto invoice = media ? media->invoice() : nullptr; @@ -87,9 +86,7 @@ void CheckoutProcess::Start( i->second->setReactivateCallback(std::move(reactivate)); i->second->setNonPanelPaymentFormProcess( std::move(nonPanelPaymentFormProcess)); - if (!hasNonPanelPaymentFormProcess) { - i->second->requestActivate(); - } + i->second->requestActivate(); return; } const auto j = processes.byItem.emplace( @@ -100,9 +97,7 @@ void CheckoutProcess::Start( std::move(reactivate), std::move(nonPanelPaymentFormProcess), PrivateTag{})).first; - if (!hasNonPanelPaymentFormProcess) { - j->second->requestActivate(); - } + j->second->requestActivate(); } void CheckoutProcess::Start( @@ -110,16 +105,13 @@ void CheckoutProcess::Start( const QString &slug, Fn reactivate, Fn nonPanelPaymentFormProcess) { - const auto hasNonPanelPaymentFormProcess = !!nonPanelPaymentFormProcess; auto &processes = LookupSessionProcesses(session); const auto i = processes.bySlug.find(slug); if (i != end(processes.bySlug)) { i->second->setReactivateCallback(std::move(reactivate)); i->second->setNonPanelPaymentFormProcess( std::move(nonPanelPaymentFormProcess)); - if (!hasNonPanelPaymentFormProcess) { - i->second->requestActivate(); - } + i->second->requestActivate(); return; } const auto j = processes.bySlug.emplace( @@ -130,20 +122,21 @@ void CheckoutProcess::Start( std::move(reactivate), std::move(nonPanelPaymentFormProcess), PrivateTag{})).first; - if (!hasNonPanelPaymentFormProcess) { - j->second->requestActivate(); - } + j->second->requestActivate(); } void CheckoutProcess::Start( InvoicePremiumGiftCode giftCodeInvoice, - Fn reactivate) { + Fn reactivate, + Fn nonPanelPaymentFormProcess) { const auto randomId = giftCodeInvoice.randomId; auto id = InvoiceId{ std::move(giftCodeInvoice) }; auto &processes = LookupSessionProcesses(SessionFromId(id)); const auto i = processes.byRandomId.find(randomId); if (i != end(processes.byRandomId)) { i->second->setReactivateCallback(std::move(reactivate)); + i->second->setNonPanelPaymentFormProcess( + std::move(nonPanelPaymentFormProcess)); i->second->requestActivate(); return; } @@ -153,7 +146,7 @@ void CheckoutProcess::Start( std::move(id), Mode::Payment, std::move(reactivate), - nullptr, + std::move(nonPanelPaymentFormProcess), PrivateTag{})).first; j->second->requestActivate(); } @@ -372,7 +365,9 @@ void CheckoutProcess::setNonPanelPaymentFormProcess( } void CheckoutProcess::requestActivate() { - _panel->requestActivate(); + if (!_nonPanelPaymentFormProcess) { + _panel->requestActivate(); + } } not_null CheckoutProcess::panelDelegate() { diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.h b/Telegram/SourceFiles/payments/payments_checkout_process.h index 8131a4533..d5f6ce945 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.h +++ b/Telegram/SourceFiles/payments/payments_checkout_process.h @@ -88,7 +88,8 @@ public: Fn nonPanelPaymentFormProcess); static void Start( InvoicePremiumGiftCode giftCodeInvoice, - Fn reactivate); + Fn reactivate, + Fn nonPanelPaymentFormProcess = nullptr); static void Start( InvoiceCredits creditsInvoice, Fn reactivate); diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 790b7a960..a08aa4f82 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -219,6 +219,13 @@ MTPinputStorePaymentPurpose InvoiceCreditsGiveawayToTL( MTP_int(invoice.users)); } +bool IsPremiumForStarsInvoice(const InvoiceId &id) { + const auto giftCode = std::get_if(&id.value); + return giftCode + && !giftCode->creditsAmount + && (giftCode->currency == ::Ui::kCreditsCurrency); +} + Form::Form(InvoiceId id, bool receipt) : _id(id) , _session(SessionFromId(id)) @@ -412,12 +419,29 @@ MTPInputInvoice Form::inputInvoice() const { MTP_long(giftCode.amount)); const auto users = std::get_if( &giftCode.purpose); - if (users) { + auto message = (users && !users->message.empty()) + ? MTP_textWithEntities( + MTP_string(users->message.text), + Api::EntitiesToMTP( + &users->users.front()->session(), + users->message.entities, + Api::ConvertOption::SkipLocal)) + : std::optional(); + if (users + && users->users.size() == 1 + && giftCode.currency == ::Ui::kCreditsCurrency) { + using Flag = MTPDinputInvoicePremiumGiftStars::Flag; + return MTP_inputInvoicePremiumGiftStars( + MTP_flags(message ? Flag::f_message : Flag()), + users->users.front()->inputUser, + MTP_int(giftCode.months), + message.value_or(MTPTextWithEntities())); + } else if (users) { using Flag = MTPDinputStorePaymentPremiumGiftCode::Flag; return MTP_inputInvoicePremiumGiftCode( MTP_inputStorePaymentPremiumGiftCode( MTP_flags((users->boostPeer ? Flag::f_boost_peer : Flag()) - | (users->message.empty() ? Flag(0) : Flag::f_message)), + | (message ? Flag::f_message : Flag())), MTP_vector_from_range(ranges::views::all( users->users ) | ranges::views::transform([](not_null user) { @@ -426,12 +450,7 @@ MTPInputInvoice Form::inputInvoice() const { users->boostPeer ? users->boostPeer->input : MTPInputPeer(), MTP_string(giftCode.currency), MTP_long(giftCode.amount), - MTP_textWithEntities( - MTP_string(users->message.text), - Api::EntitiesToMTP( - &users->users.front()->session(), - users->message.entities, - Api::ConvertOption::SkipLocal))), + message.value_or(MTPTextWithEntities())), option); } else { return MTP_inputInvoicePremiumGiftCode( diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index 8dae0d008..8fa322c72 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -287,6 +287,8 @@ struct FormUpdate : std::variant< [[nodiscard]] MTPinputStorePaymentPurpose InvoiceCreditsGiveawayToTL( const InvoicePremiumGiftCode &invoice); +[[nodiscard]] bool IsPremiumForStarsInvoice(const InvoiceId &id); + class Form final : public base::has_weak_ptr { public: Form(InvoiceId id, bool receipt); diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp index c76430820..d63b505a9 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -57,7 +57,8 @@ void ProcessCreditsPayment( onstack(CheckoutResult::Cancelled); } return; - } else if (form->starGiftForm) { + } else if (form->starGiftForm + || IsPremiumForStarsInvoice(form->id)) { const auto done = [=](std::optional error) { const auto onstack = maybeReturnToBot; if (error) { @@ -86,7 +87,7 @@ void ProcessCreditsPayment( onstack(CheckoutResult::Paid); } }; - Ui::SendStarGift(&show->session(), form, done); + Ui::SendStarsForm(&show->session(), form, done); return; } const auto unsuccessful = std::make_shared(true); diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 0ac5c025a..f492a6e5e 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -429,8 +429,7 @@ void Credits::setupContent() { Ui::AddSkip(content); struct State final { - rpl::variable confirmButtonBusy = false; - std::optional api; + BuyStarsHandler buyStars; }; const auto state = content->lifetime().make_state(); @@ -438,60 +437,21 @@ void Credits::setupContent() { object_ptr( content, rpl::conditional( - state->confirmButtonBusy.value(), + state->buyStars.loadingValue(), rpl::single(QString()), tr::lng_credits_buy_button()), st::creditsSettingsBigBalanceButton), st::boxRowPadding); button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); const auto show = _controller->uiShow(); - const auto optionsBox = [=](not_null box) { - box->setStyle(st::giveawayGiftCodeBox); - box->setWidth(st::boxWideWidth); - box->setTitle(tr::lng_credits_summary_options_subtitle()); - const auto inner = box->verticalLayout(); - const auto self = show->session().user(); - const auto options = state->api - ? state->api->options() - : Data::CreditTopupOptions(); - const auto amount = StarsAmount(); - FillCreditOptions(show, inner, self, amount, paid, nullptr, options); - - const auto button = box->addButton(tr::lng_close(), [=] { - box->closeBox(); - }); - const auto buttonWidth = st::boxWideWidth - - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding); - button->widthValue() | rpl::filter([=] { - return (button->widthNoMargins() != buttonWidth); - }) | rpl::start_with_next([=] { - button->resizeToWidth(buttonWidth); - }, button->lifetime()); - }; - button->setClickedCallback([=] { - if (state->api && !state->api->options().empty()) { - state->confirmButtonBusy = false; - show->show(Box(optionsBox)); - } else { - state->confirmButtonBusy = true; - state->api.emplace(show->session().user()); - state->api->request( - ) | rpl::start_with_error_done([=](const QString &error) { - state->confirmButtonBusy = false; - show->showToast(error); - }, [=] { - state->confirmButtonBusy = false; - show->show(Box(optionsBox)); - }, content->lifetime()); - } - }); + button->setClickedCallback(state->buyStars.handler(show, paid)); { using namespace Info::Statistics; const auto loadingAnimation = InfiniteRadialAnimationWidget( button, button->height() / 2); AddChildToWidgetCenter(button, loadingAnimation); - loadingAnimation->showOn(state->confirmButtonBusy.value()); + loadingAnimation->showOn(state->buyStars.loadingValue()); } const auto paddings = rect::m::sum::h(st::boxRowPadding); button->widthValue() | rpl::filter([=] { @@ -699,4 +659,62 @@ Type CreditsId() { return Credits::Id(); } +Fn BuyStarsHandler::handler( + std::shared_ptr<::Main::SessionShow> show, + Fn paid) { + const auto optionsBox = [=](not_null box) { + box->setStyle(st::giveawayGiftCodeBox); + box->setWidth(st::boxWideWidth); + box->setTitle(tr::lng_credits_summary_options_subtitle()); + const auto inner = box->verticalLayout(); + const auto self = show->session().user(); + const auto options = _api + ? _api->options() + : Data::CreditTopupOptions(); + const auto amount = StarsAmount(); + const auto weak = Ui::MakeWeak(box); + FillCreditOptions(show, inner, self, amount, [=] { + if (const auto strong = weak.data()) { + strong->closeBox(); + } + if (const auto onstack = paid) { + onstack(); + } + }, nullptr, options); + + const auto button = box->addButton(tr::lng_close(), [=] { + box->closeBox(); + }); + const auto buttonWidth = st::boxWideWidth + - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding); + button->widthValue() | rpl::filter([=] { + return (button->widthNoMargins() != buttonWidth); + }) | rpl::start_with_next([=] { + button->resizeToWidth(buttonWidth); + }, button->lifetime()); + }; + return crl::guard(this, [=] { + if (_api && !_api->options().empty()) { + _loading = false; + show->show(Box(crl::guard(this, optionsBox))); + } else { + _loading = true; + const auto user = show->session().user(); + _api = std::make_unique(user); + _api->request( + ) | rpl::start_with_error_done([=](const QString &error) { + _loading = false; + show->showToast(error); + }, [=] { + _loading = false; + show->show(Box(crl::guard(this, optionsBox))); + }, _lifetime); + } + }); +} + +rpl::producer BuyStarsHandler::loadingValue() const { + return _loading.value(); +} + } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_credits.h b/Telegram/SourceFiles/settings/settings_credits.h index c0e32e1be..480ced9e6 100644 --- a/Telegram/SourceFiles/settings/settings_credits.h +++ b/Telegram/SourceFiles/settings/settings_credits.h @@ -9,9 +9,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_type.h" +namespace Api { +class CreditsTopupOptions; +} // namespace Api + +namespace Main { +class SessionShow; +} // namespace Main + namespace Settings { [[nodiscard]] Type CreditsId(); -} // namespace Settings +class BuyStarsHandler final : public base::has_weak_ptr { +public: + [[nodiscard]] Fn handler( + std::shared_ptr<::Main::SessionShow> show, + Fn paid = nullptr); + [[nodiscard]] rpl::producer loadingValue() const; +private: + std::unique_ptr _api; + rpl::variable _loading; + rpl::lifetime _lifetime; + +}; + +} // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 62075809f..c6f3d15d5 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -569,8 +569,8 @@ void FillCreditOptions( if (const auto strong = weak.data()) { strong->window()->setFocus(); if (result == Payments::CheckoutResult::Paid) { - if (paid) { - paid(); + if (const auto onstack = paid) { + onstack(); } } } diff --git a/Telegram/SourceFiles/ui/controls/send_button.cpp b/Telegram/SourceFiles/ui/controls/send_button.cpp index 8c692403b..67c74b81f 100644 --- a/Telegram/SourceFiles/ui/controls/send_button.cpp +++ b/Telegram/SourceFiles/ui/controls/send_button.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/ui_utility.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" +#include "styles/style_credits.h" namespace Ui { namespace { @@ -54,7 +55,7 @@ void SendButton::setState(State state) { || _state.starsToSend != state.starsToSend) { _starsToSendText.setMarkedText( _st.stars.style, - Text::IconEmoji(&st::boxStarIconEmoji).append( + Text::IconEmoji(&st::starIconEmoji).append( Lang::FormatCountToShort(state.starsToSend).string), kMarkupTextOptions); } diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 2132c79f6..8add6b1dc 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -63,11 +63,12 @@ creditsBoxButtonLabel: FlatLabel(defaultFlatLabel) { } starIconEmoji: IconEmoji { - icon: icon{{ "payments/small_star", windowFg }}; - padding: margins(0px, -3px, 0px, 0px); + icon: icon{{ "payments/premium_emoji", creditsBg1 }}; + padding: margins(4px, 1px, 4px, 0px); +} +starIconEmojiColored: IconEmoji(starIconEmoji) { + useIconColor: true; } -starIconSmall: icon{{ "payments/small_star", windowFg }}; -starIconSmallPadding: margins(0px, -3px, 0px, 0px); creditsHistoryEntryTypeAds: icon {{ "folders/folders_channels", premiumButtonFg }}; @@ -123,10 +124,17 @@ giftBoxGiftHeight: 164px; giftBoxGiftSmall: 108px; giftBoxGiftRadius: 12px; giftBoxGiftBadgeFont: font(10px semibold); +giftBoxByStarsStyle: TextStyle(defaultTextStyle) { + font: font(10px); +} +giftBoxByStarsSkip: 2px; +giftBoxByStarsStarTop: 3px; giftBoxPremiumIconSize: 64px; giftBoxPremiumIconTop: 10px; giftBoxPremiumTextTop: 84px; +giftBoxPremiumTextTopByStars: 78px; giftBoxButtonBottom: 12px; +giftBoxButtonBottomByStars: 18px; giftBoxButtonPadding: margins(8px, 4px, 8px, 4px); giftBoxPreviewStickerPadding: margins(10px, 12px, 10px, 16px); giftBoxPreviewTitlePadding: margins(12px, 4px, 12px, 4px); @@ -135,6 +143,7 @@ giftBoxButtonMargin: margins(12px, 8px, 12px, 12px); giftBoxStickerTop: 0px; giftBoxStickerStarTop: 24px; giftBoxSmallStickerTop: 16px; +giftBoxStickerTopByStars: -4px; giftBoxStickerSize: size(80px, 80px); giftBoxUserpicSize: 24px; giftBoxUserpicSkip: 2px; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index da0d634ec..76f254814 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit da0d634ec373d8ba0b8f66f3381a87e43edffdf2 +Subproject commit 76f25481470faee2e0a24aa7be8c6d58564addea From 0dd8ae3d77fb456bc16b35e76fd1a9319f896b8e Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 4 Mar 2025 14:22:08 +0400 Subject: [PATCH 077/103] Update submodules. --- Telegram/lib_base | 2 +- Telegram/lib_lottie | 2 +- Telegram/lib_webview | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/lib_base b/Telegram/lib_base index 90a358695..b28088164 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 90a358695a6188cd80f640e1b6b8faab40c9221b +Subproject commit b28088164b7a46c70ae2cfd9daf865f6425610b2 diff --git a/Telegram/lib_lottie b/Telegram/lib_lottie index 1a700e5a0..3eb4a97f1 160000 --- a/Telegram/lib_lottie +++ b/Telegram/lib_lottie @@ -1 +1 @@ -Subproject commit 1a700e5a0d7c3e2f617530354ff2a47c5c72bb4a +Subproject commit 3eb4a97f1dd038bc4b6bd2884262242382a37e79 diff --git a/Telegram/lib_webview b/Telegram/lib_webview index a0b1afdbd..f54696991 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit a0b1afdbdad2017075d05bf4c3054a399ea55e0b +Subproject commit f546969919a5946d49a504f8159041fa5b55c3df From f4c739ab92a7878f97f1c2045f56244c687a8980 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 4 Mar 2025 18:43:06 +0400 Subject: [PATCH 078/103] Improve transactions history for new stuff. --- Telegram/Resources/langs/lang.strings | 6 ++ Telegram/SourceFiles/api/api_credits.cpp | 20 ++++- .../SourceFiles/boxes/gift_premium_box.cpp | 30 ++++++-- .../boxes/peers/edit_peer_info_box.cpp | 6 ++ .../boxes/peers/edit_peer_info_box.h | 4 + .../SourceFiles/core/local_url_handlers.cpp | 24 ++++++ Telegram/SourceFiles/core/stars_amount.h | 9 +++ Telegram/SourceFiles/data/data_credits.h | 8 +- .../peer_gifts/info_peer_gifts_common.cpp | 4 +- .../info_statistics_list_controllers.cpp | 19 ++--- .../settings/settings_credits_graphics.cpp | 73 ++++++++++++++----- .../ui/effects/credits_graphics.cpp | 10 ++- 12 files changed, 171 insertions(+), 42 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 250ba9584..d5abcfbad 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2681,6 +2681,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_summary_history_entry_inner_in" = "In-App Purchase"; "lng_credits_summary_balance" = "Balance"; "lng_credits_commission" = "{amount} commission"; +"lng_credits_paid_messages_fee#one" = "Fee for {count} Message"; +"lng_credits_paid_messages_fee#other" = "Fee for {count} Messages"; +"lng_credits_paid_messages_fee_about" = "You receive {percent} of the price that you charge for each incoming message. {link}"; +"lng_credits_paid_messages_fee_about_link" = "Change Fee {emoji}"; +"lng_credits_paid_messages_full" = "Full Price"; +"lng_credits_premium_gift_duration" = "Duration"; "lng_credits_more_options" = "More Options"; "lng_credits_balance_me" = "your balance"; "lng_credits_buy_button" = "Buy More Stars"; diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index bdb10d02d..0dc21c636 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -90,7 +90,13 @@ constexpr auto kTransactionsLimit = 100; ? peerFromMTP(*tl.data().vstarref_peer()).value : 0; const auto incoming = (amount >= StarsAmount()); - const auto saveActorId = (reaction || !extended.empty()) && incoming; + const auto paidMessagesCount + = tl.data().vpaid_messages().value_or_empty(); + const auto premiumMonthsForStars + = tl.data().vpremium_gift_months().value_or_empty(); + const auto saveActorId = (reaction + || !extended.empty() + || paidMessagesCount) && incoming; const auto parsedGift = stargift ? FromTL(&peer->session(), *stargift) : std::optional(); @@ -110,9 +116,9 @@ constexpr auto kTransactionsLimit = 100; .bareGiftStickerId = giftStickerId, .bareActorId = saveActorId ? barePeerId : uint64(0), .uniqueGift = parsedGift ? parsedGift->unique : nullptr, - .starrefAmount = starrefAmount, - .starrefCommission = starrefCommission, - .starrefRecipientId = starrefBarePeerId, + .starrefAmount = paidMessagesCount ? StarsAmount() : starrefAmount, + .starrefCommission = paidMessagesCount ? 0 : starrefCommission, + .starrefRecipientId = paidMessagesCount ? 0 : starrefBarePeerId, .peerType = tl.data().vpeer().match([](const HistoryPeerTL &) { return Data::CreditsHistoryEntry::PeerType::Peer; }, [](const MTPDstarsTransactionPeerPlayMarket &) { @@ -138,9 +144,15 @@ constexpr auto kTransactionsLimit = 100; ? base::unixtime::parse(tl.data().vtransaction_date()->v) : QDateTime(), .successLink = qs(tl.data().vtransaction_url().value_or_empty()), + .paidMessagesCount = paidMessagesCount, + .paidMessagesAmount = (paidMessagesCount + ? starrefAmount + : StarsAmount()), + .paidMessagesCommission = paidMessagesCount ? starrefCommission : 0, .starsConverted = int(nonUniqueGift ? nonUniqueGift->vconvert_stars().v : 0), + .premiumMonthsForStars = premiumMonthsForStars, .floodSkip = int(tl.data().vfloodskip_number().value_or(0)), .converted = stargift && incoming, .stargift = stargift.has_value(), diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index db9eaf3a9..1d12e7f81 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox. #include "boxes/star_gift_box.h" // ShowStarGiftBox. #include "boxes/transfer_gift_box.h" // ShowTransferGiftBox. +#include "core/ui_integration.h" #include "data/data_boosts.h" #include "data/data_changes.h" #include "data/data_channel.h" @@ -58,7 +59,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/gradient_round_button.h" -#include "ui/widgets/label_with_custom_emoji.h" #include "ui/widgets/tooltip.h" #include "ui/wrap/padding_wrap.h" #include "ui/wrap/slide_wrap.h" @@ -66,6 +66,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_peer_menu.h" // ShowChooseRecipientBox. #include "window/window_session_controller.h" #include "styles/style_boxes.h" +#include "styles/style_credits.h" #include "styles/style_giveaway.h" #include "styles/style_info.h" #include "styles/style_layers.h" @@ -1272,8 +1273,8 @@ void AddStarGiftTable( const auto selfBareId = session->userPeerId().value; const auto giftToSelf = (peerId == session->userPeerId()) && (entry.in || entry.bareGiftOwnerId == selfBareId); - const auto giftToChannel = entry.giftSavedId - && peerIsChannel(PeerId(entry.bareGiftListPeerId)); + const auto giftToChannel = entry.giftChannelSavedId + && peerIsChannel(PeerId(entry.bareEntryOwnerId)); const auto raw = std::make_shared(nullptr); const auto showTooltip = [=]( @@ -1394,14 +1395,14 @@ void AddStarGiftTable( ? MakePeerTableValue(table, show, PeerId(entry.bareActorId)) : MakeHiddenPeerTableValue(table)), st::giveawayGiftCodePeerMargin); - if (entry.bareGiftListPeerId) { + if (entry.bareEntryOwnerId) { AddTableRow( table, tr::lng_credits_box_history_entry_peer(), MakePeerTableValue( table, show, - PeerId(entry.bareGiftListPeerId)), + PeerId(entry.bareEntryOwnerId)), st::giveawayGiftCodePeerMargin); } } else if (peerId && !giftToSelf) { @@ -1775,6 +1776,25 @@ void AddCreditsHistoryEntryTable( tr::lng_credits_box_history_entry_subscription( Ui::Text::WithEntities)); } + if (entry.paidMessagesAmount) { + auto value = Ui::Text::IconEmoji(&st::starIconEmojiColored); + const auto full = (entry.in ? 1 : -1) + * (entry.credits + entry.paidMessagesAmount); + const auto starsText = Lang::FormatStarsAmountDecimal(full); + AddTableRow( + table, + tr::lng_credits_paid_messages_full(), + rpl::single(value.append(' ' + starsText))); + } + if (const auto months = entry.premiumMonthsForStars) { + AddTableRow( + table, + tr::lng_credits_premium_gift_duration(), + tr::lng_months( + lt_count, + rpl::single(1. * months), + Ui::Text::WithEntities)); + } if (!entry.id.isEmpty()) { auto label = MakeMaybeMultilineTokenValue(table, entry.id, st); label->setClickHandlerFilter([=](const auto &...) { diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 0299e7f9b..a457f084e 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -2724,3 +2724,9 @@ bool EditPeerInfoBox::Available(not_null peer) { return false; } } + +void ShowEditChatPermissions( + not_null navigation, + not_null peer) { + ShowEditPermissions(navigation, peer); +} diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.h index 9844320cf..b8787afac 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.h @@ -56,3 +56,7 @@ private: not_null _peer; }; + +void ShowEditChatPermissions( + not_null navigation, + not_null peer); diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index c1e678579..adc083d79 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/edit_birthday_box.h" #include "ui/integration.h" #include "payments/payments_non_panel_process.h" +#include "boxes/peers/edit_peer_info_box.h" #include "boxes/share_box.h" #include "boxes/connection_box.h" #include "boxes/gift_premium_box.h" @@ -1014,6 +1015,25 @@ bool CopyUsername( return true; } +bool EditPaidMessagesFee( + Window::SessionController *controller, + const Match &match, + const QVariant &context) { + if (!controller) { + return false; + } + const auto peerId = PeerId(match->captured(1).toULongLong()); + if (const auto id = peerToChannel(peerId)) { + const auto channel = controller->session().data().channelLoaded(id); + if (channel && channel->canEditPermissions()) { + ShowEditChatPermissions(controller, channel); + } + } else { + controller->show(Box(EditMessagesPrivacyBox, controller)); + } + return true; +} + bool ShowStarsExamples( Window::SessionController *controller, const Match &match, @@ -1517,6 +1537,10 @@ const std::vector &InternalUrlHandlers() { u"^username_regular/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q, CopyUsername, }, + { + u"^edit_paid_messages_fee/([0-9]+)$"_q, + EditPaidMessagesFee, + }, { u"^stars_examples$"_q, ShowStarsExamples, diff --git a/Telegram/SourceFiles/core/stars_amount.h b/Telegram/SourceFiles/core/stars_amount.h index fc9c8df90..ca0e6fbe2 100644 --- a/Telegram/SourceFiles/core/stars_amount.h +++ b/Telegram/SourceFiles/core/stars_amount.h @@ -60,6 +60,11 @@ public: normalize(); return *this; } + inline StarsAmount operator-() const { + auto result = *this; + result *= -1; + return result; + } friend inline auto operator<=>(StarsAmount, StarsAmount) = default; friend inline bool operator==(StarsAmount, StarsAmount) = default; @@ -97,3 +102,7 @@ private: [[nodiscard]] inline StarsAmount operator*(StarsAmount a, int64 b) { return a *= b; } + +[[nodiscard]] inline StarsAmount operator*(int64 a, StarsAmount b) { + return b *= a; +} diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index adf692218..f569cdd97 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -66,8 +66,8 @@ struct CreditsHistoryEntry final { uint64 bareGiftStickerId = 0; uint64 bareGiftOwnerId = 0; uint64 bareActorId = 0; - uint64 bareGiftListPeerId = 0; - uint64 giftSavedId = 0; + uint64 bareEntryOwnerId = 0; + uint64 giftChannelSavedId = 0; uint64 stargiftId = 0; std::shared_ptr uniqueGift; StarsAmount starrefAmount; @@ -77,11 +77,15 @@ struct CreditsHistoryEntry final { QDateTime subscriptionUntil; QDateTime successDate; QString successLink; + int paidMessagesCount = 0; + StarsAmount paidMessagesAmount; + int paidMessagesCommission = 0; int limitedCount = 0; int limitedLeft = 0; int starsConverted = 0; int starsToUpgrade = 0; int starsUpgradedBySender = 0; + int premiumMonthsForStars = 0; int floodSkip = 0; bool converted : 1 = false; bool anonymous : 1 = false; diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp index 98b521566..dfd323656 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp @@ -100,7 +100,7 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) { data.currency, true)); if (const auto stars = data.stars) { - const auto starsText = QString::number(stars); + const auto starsText = Lang::FormatCountDecimal(stars); _byStars.setMarkedText( st::giftBoxByStarsStyle, tr::lng_gift_premium_by_stars( @@ -139,7 +139,7 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) { (unique ? tr::lng_gift_price_unique(tr::now, Ui::Text::WithEntities) : _delegate->star().append( - ' ' + QString::number(data.info.stars))), + ' ' + Lang::FormatCountDecimal(data.info.stars))), kMarkupTextOptions, _delegate->textContext()); if (!_stars) { diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 799416844..e08bc563e 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -797,7 +797,6 @@ private: PaintRoundImageCallback _paintUserpicCallback; std::optional _rightLabel; - QString _title; QString _name; Ui::Text::String _description; @@ -850,9 +849,13 @@ void CreditsRow::init() { const auto name = !isSpecial ? PeerListRow::generateName() : Ui::GenerateEntryName(_entry).text; - _name = _entry.title.isEmpty() - ? name - : (!_entry.subscriptionUntil.isNull() && !isSpecial) + _name = _entry.paidMessagesCount + ? tr::lng_credits_paid_messages_fee( + tr::now, + lt_count, + _entry.paidMessagesCount) + : ((!_entry.subscriptionUntil.isNull() && !isSpecial) + || _entry.title.isEmpty()) ? name : _entry.title; setSkipPeerBadge(true); @@ -861,6 +864,8 @@ void CreditsRow::init() { tr::now, lt_count_decimal, _entry.floodSkip) + : _entry.paidMessagesCount + ? name : (!_entry.subscriptionUntil.isNull() && !_entry.title.isEmpty()) ? _entry.title : _entry.refunded @@ -943,11 +948,7 @@ const Data::SubscriptionEntry &CreditsRow::subscription() const { } QString CreditsRow::generateName() { - return (!_entry.title.isEmpty() && !_entry.subscriptionUntil.isNull()) - ? _name - : _entry.title.isEmpty() - ? _name - : _entry.title; + return _name; } PaintRoundImageCallback CreditsRow::generatePaintUserpicCallback(bool force) { diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index c6f3d15d5..d42f40ee8 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -178,10 +178,10 @@ private: const Data::CreditsHistoryEntry &entry) { return !entry.stargift ? Data::SavedStarGiftId() - : (entry.bareGiftListPeerId && entry.giftSavedId) + : (entry.bareEntryOwnerId && entry.giftChannelSavedId) ? Data::SavedStarGiftId::Chat( - session->data().peer(PeerId(entry.bareGiftListPeerId)), - entry.giftSavedId) + session->data().peer(PeerId(entry.bareEntryOwnerId)), + entry.giftChannelSavedId) : Data::SavedStarGiftId::User(MsgId(entry.bareMsgId)); } @@ -950,9 +950,9 @@ void GenericCreditsEntryBox( const auto giftToSelf = isStarGift && (e.barePeerId == selfPeerId) && (e.in || e.bareGiftOwnerId == selfPeerId); - const auto giftChannel = (isStarGift && e.giftSavedId) + const auto giftChannel = (isStarGift && e.giftChannelSavedId) ? session->data().peer( - PeerId(e.bareGiftListPeerId))->asChannel() + PeerId(e.bareEntryOwnerId))->asChannel() : nullptr; const auto giftToChannel = (giftChannel != nullptr); const auto giftToChannelCanManage = giftToChannel @@ -1051,7 +1051,7 @@ void GenericCreditsEntryBox( content->add(object_ptr>( content, GenericEntryPhoto(content, callback, stUser.photoSize))); - } else if (peer && !e.gift) { + } else if (peer && !e.gift && !e.premiumMonthsForStars) { if (e.subscriptionUntil.isNull() && s.until.isNull()) { content->add(object_ptr>( content, @@ -1061,7 +1061,7 @@ void GenericCreditsEntryBox( content, SubscriptionUserpic(content, peer, stUser.photoSize))); } - } else if (e.gift || isPrize) { + } else if (e.gift || isPrize || e.premiumMonthsForStars) { struct State final { DocumentData *sticker = nullptr; std::shared_ptr media; @@ -1079,7 +1079,9 @@ void GenericCreditsEntryBox( auto &packs = session->giftBoxStickersPacks(); const auto document = starGiftSticker ? starGiftSticker - : packs.lookup(packs.monthsForStars(e.credits.whole())); + : packs.lookup(e.premiumMonthsForStars + ? e.premiumMonthsForStars + : packs.monthsForStars(e.credits.whole())); if (document && document->sticker()) { state->sticker = document; state->media = document->createMediaView(); @@ -1159,6 +1161,13 @@ void GenericCreditsEntryBox( ? tr::lng_credits_box_history_entry_giveaway_name(tr::now) : (!e.subscriptionUntil.isNull() && e.title.isEmpty()) ? tr::lng_credits_box_history_entry_subscription(tr::now) + : e.paidMessagesCount + ? tr::lng_credits_paid_messages_fee( + tr::now, + lt_count, + e.paidMessagesCount) + : e.premiumMonthsForStars + ? tr::lng_premium_summary_title(tr::now) : !e.title.isEmpty() ? e.title : e.starrefCommission @@ -1315,6 +1324,12 @@ void GenericCreditsEntryBox( rpl::single(e.description), st::creditsBoxAbout))); } + + const auto arrowEmoji = Ui::Text::SingleCustomEmoji( + owner->customEmojiManager().registerInternalEmoji( + st::topicButtonArrow, + st::channelEarnLearnArrowMargins, + true)); if (!uniqueGift && starGiftCanManage) { Ui::AddSkip(content); const auto about = box->addRow( @@ -1380,14 +1395,9 @@ void GenericCreditsEntryBox( } else if (isStarGift) { } else if (e.gift || isPrize) { Ui::AddSkip(content); - const auto arrow = Ui::Text::SingleCustomEmoji( - owner->customEmojiManager().registerInternalEmoji( - st::topicButtonArrow, - st::channelEarnLearnArrowMargins, - true)); auto link = tr::lng_credits_box_history_entry_gift_about_link( lt_emoji, - rpl::single(arrow), + rpl::single(arrowEmoji), Ui::Text::RichLangValue ) | rpl::map([](TextWithEntities text) { return Ui::Text::Link( @@ -1411,6 +1421,31 @@ void GenericCreditsEntryBox( Ui::Text::RichLangValue), { .session = session }, st::creditsBoxAbout))); + } else if (e.paidMessagesCommission && e.barePeerId) { + Ui::AddSkip(content); + auto link = tr::lng_credits_paid_messages_fee_about_link( + lt_emoji, + rpl::single(arrowEmoji), + Ui::Text::RichLangValue + ) | rpl::map([id = e.barePeerId](TextWithEntities text) { + return Ui::Text::Link( + std::move(text), + u"internal:edit_paid_messages_fee/"_q + QString::number(id)); + }); + const auto percent = 100. - (e.paidMessagesCommission / 10.); + box->addRow(object_ptr>( + box, + Ui::CreateLabelWithCustomEmoji( + box, + tr::lng_credits_paid_messages_fee_about( + lt_percent, + rpl::single( + Ui::Text::Bold(QString::number(percent) + '%')), + lt_link, + std::move(link), + Ui::Text::RichLangValue), + { .session = session }, + st::creditsBoxAbout))); } Ui::AddSkip(content); @@ -1695,7 +1730,7 @@ void GenericCreditsEntryBox( : canUpgradeFree ? tr::lng_gift_upgrade_free() : (canToggle && !e.savedToProfile) - ? (e.giftSavedId + ? (e.giftChannelSavedId ? tr::lng_gift_show_on_channel : tr::lng_gift_show_on_page)() : tr::lng_box_ok())); @@ -1882,8 +1917,8 @@ void SavedStarGiftBox( .bareGiftStickerId = data.info.document->id, .bareGiftOwnerId = owner->id.value, .bareActorId = data.fromId.value, - .bareGiftListPeerId = chatGiftPeer ? chatGiftPeer->id.value : 0, - .giftSavedId = data.manageId.chatSavedId(), + .bareEntryOwnerId = chatGiftPeer ? chatGiftPeer->id.value : 0, + .giftChannelSavedId = data.manageId.chatSavedId(), .stargiftId = data.info.id, .uniqueGift = data.info.unique, .peerType = Data::CreditsHistoryEntry::PeerType::Peer, @@ -1927,8 +1962,8 @@ void StarGiftViewBox( ? data.unique->ownerId.value : toId.value), .bareActorId = (toChannel ? data.channelFrom->id.value : 0), - .bareGiftListPeerId = (toChannel ? data.channel->id.value : 0), - .giftSavedId = data.channelSavedId, + .bareEntryOwnerId = (toChannel ? data.channel->id.value : 0), + .giftChannelSavedId = data.channelSavedId, .stargiftId = data.stargiftId, .uniqueGift = data.unique, .peerType = Data::CreditsHistoryEntry::PeerType::Peer, diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index d472d6867..0fbf095cc 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -554,7 +554,15 @@ TextWithEntities GenerateEntryName(const Data::CreditsHistoryEntry &entry) { Info::BotStarRef::FormatCommission(entry.starrefCommission) }, TextWithEntities::Simple) - : (entry.floodSkip + : entry.paidMessagesCount + ? tr::lng_credits_paid_messages_fee( + tr::now, + lt_count, + entry.paidMessagesCount, + TextWithEntities::Simple) + : (entry.premiumMonthsForStars + ? tr::lng_premium_summary_title + : entry.floodSkip ? tr::lng_credits_box_history_entry_api : entry.reaction ? tr::lng_credits_box_history_entry_reaction_name From 51dc5d6e377e0dd735e1086d0443e2cc05d4f964 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 3 Mar 2025 19:07:46 +0300 Subject: [PATCH 079/103] Fixed discount calculation for gifts options with different currencies. --- .../SourceFiles/api/api_premium_option.cpp | 2 +- Telegram/SourceFiles/api/api_premium_option.h | 21 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/api/api_premium_option.cpp b/Telegram/SourceFiles/api/api_premium_option.cpp index bd3056a75..d3c67e23b 100644 --- a/Telegram/SourceFiles/api/api_premium_option.cpp +++ b/Telegram/SourceFiles/api/api_premium_option.cpp @@ -26,7 +26,7 @@ Data::PremiumSubscriptionOption CreateSubscriptionOption( }(); return { .duration = Ui::FormatTTL(months * 86400 * 31), - .discount = discount + .discount = (discount > 0) ? QString::fromUtf8("\xe2\x88\x92%1%").arg(discount) : QString(), .costPerMonth = Ui::FillAmountAndCurrency( diff --git a/Telegram/SourceFiles/api/api_premium_option.h b/Telegram/SourceFiles/api/api_premium_option.h index afe66ac4a..2648a7f9c 100644 --- a/Telegram/SourceFiles/api/api_premium_option.h +++ b/Telegram/SourceFiles/api/api_premium_option.h @@ -24,15 +24,26 @@ template if (tlOpts.isEmpty()) { return {}; } + auto monthlyAmountPerCurrency = base::flat_map(); auto result = Data::PremiumSubscriptionOptions(); - const auto monthlyAmount = [&] { + const auto monthlyAmount = [&](const QString ¤cy) -> int { + const auto it = monthlyAmountPerCurrency.find(currency); + if (it != end(monthlyAmountPerCurrency)) { + return it->second; + } const auto &min = ranges::min_element( tlOpts, ranges::less(), - [](const Option &o) { return o.data().vamount().v; } + [&](const Option &o) { + return currency == qs(o.data().vcurrency()) + ? o.data().vamount().v + : std::numeric_limits::max(); + } )->data(); - return min.vamount().v / float64(min.vmonths().v); - }(); + const auto monthly = min.vamount().v / float64(min.vmonths().v); + monthlyAmountPerCurrency.emplace(currency, monthly); + return monthly; + }; result.reserve(tlOpts.size()); for (const auto &tlOption : tlOpts) { const auto &option = tlOption.data(); @@ -45,7 +56,7 @@ template const auto currency = qs(option.vcurrency()); result.push_back(CreateSubscriptionOption( months, - monthlyAmount, + monthlyAmount(currency), amount, currency, botUrl)); From 9491cff1dfc234d3e127d98d51e3e04c8256ca20 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 3 Mar 2025 19:56:34 +0300 Subject: [PATCH 080/103] Fixed display credits in list of gift options. --- .../ui/effects/premium_graphics.cpp | 69 +++++++++++++++++-- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp index fb35336b3..ed73836f3 100644 --- a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "ui/abstract_button.h" #include "ui/effects/animations.h" +#include "ui/effects/credits_graphics.h" #include "ui/effects/gradient.h" #include "ui/effects/numbers_animation.h" #include "ui/effects/premium_bubble.h" @@ -917,6 +918,45 @@ void AddGiftOptions( }, *onceLifetime); } + constexpr auto kStar = QChar(0x2B50); + const auto removedStar = [&](QString s) { + return s.replace(kStar, QChar()); + }; + const auto &costPerMonthFont = st::shareBoxListItem.nameStyle.font; + const auto &costTotalFont = st::normalFont; + const auto costPerMonthIcon = info.costPerMonth.startsWith(kStar) + ? GenerateStars(costPerMonthFont->height, 1) + : QImage(); + const auto costPerMonthText = costPerMonthIcon.isNull() + ? info.costPerMonth + : removedStar(info.costPerMonth); + const auto costTotalEntry = [&] { + if (!info.costTotal.startsWith(kStar)) { + return QImage(); + } + const auto text = removedStar(info.costTotal); + const auto icon = GenerateStars(costTotalFont->height, 1); + auto result = QImage( + QSize(costTotalFont->spacew + costTotalFont->width(text), 0) + * style::DevicePixelRatio() + + icon.size(), + QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(style::DevicePixelRatio()); + result.fill(Qt::transparent); + { + auto p = QPainter(&result); + p.drawImage(0, 0, icon); + p.setPen(st::windowSubTextFg); + p.setFont(costTotalFont); + auto copy = info.costTotal; + p.drawText( + Rect(result.size() / style::DevicePixelRatio()), + text, + style::al_right); + } + return result; + }(); + row->paintRequest( ) | rpl::start_with_next([=](const QRect &r) { auto p = QPainter(row); @@ -979,7 +1019,11 @@ void AddGiftOptions( if (st.borderWidth && (animation->nowIndex == index)) { const auto progress = animation->animation.value(1.); const auto w = row->width(); - auto gradient = QLinearGradient(w - w * progress, 0, w * 2, 0); + auto gradient = QLinearGradient( + w - w * progress, + 0, + w * 2, + 0); gradient.setSpread(QGradient::Spread::RepeatSpread); gradient.setStops(stops); const auto pen = QPen( @@ -1004,13 +1048,28 @@ void AddGiftOptions( : bottomLeftRect.width() + discountMargins.left(), 0); p.setPen(st::windowSubTextFg); - p.setFont(st::shareBoxListItem.nameStyle.font); - p.drawText(perRect, info.costPerMonth, style::al_left); + p.setFont(costPerMonthFont); + const auto perMonthLeft = costPerMonthFont->spacew + + costPerMonthIcon.width() / style::DevicePixelRatio(); + p.drawText( + perRect.translated(perMonthLeft, 0), + costPerMonthText, + style::al_left); + p.drawImage(perRect.topLeft(), costPerMonthIcon); const auto totalRect = row->rect() - QMargins(0, 0, st.rowMargins.right(), 0); - p.setFont(st::normalFont); - p.drawText(totalRect, info.costTotal, style::al_right); + if (costTotalEntry.isNull()) { + p.setFont(costTotalFont); + p.drawText(totalRect, info.costTotal, style::al_right); + } else { + const auto size = costTotalEntry.size() + / style::DevicePixelRatio(); + p.drawImage( + totalRect.width() - size.width(), + (row->height() - size.height()) / 2, + costTotalEntry); + } }, row->lifetime()); row->setClickedCallback([=, duration = st::defaultCheck.duration] { From 0d8ae7bb37985cf6114a700d86689d948179fa68 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 3 Mar 2025 21:23:32 +0300 Subject: [PATCH 081/103] Renamed creditsAmount in invoice for premium gift with represented name. --- Telegram/SourceFiles/api/api_premium.cpp | 2 +- .../channel_statistics/boosts/create_giveaway_box.cpp | 4 ++-- Telegram/SourceFiles/payments/payments_form.cpp | 8 ++++---- Telegram/SourceFiles/payments/payments_form.h | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index daa9d83cb..313ec1bd1 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -529,7 +529,7 @@ rpl::producer PremiumGiftCodeOptions::applyPrepaid( _api.request(MTPpayments_LaunchPrepaidGiveaway( _peer->input, MTP_long(prepaidId), - invoice.creditsAmount + invoice.giveawayCredits ? Payments::InvoiceCreditsGiveawayToTL(invoice) : Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice) )).done([=](const MTPUpdates &result) { diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/channel_statistics/boosts/create_giveaway_box.cpp index 2a4abeb28..a5a73614e 100644 --- a/Telegram/SourceFiles/info/channel_statistics/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/boosts/create_giveaway_box.cpp @@ -1402,7 +1402,7 @@ void CreateGiveawayBox( auto invoice = [&] { if (isPrepaidCredits) { return Payments::InvoicePremiumGiftCode{ - .creditsAmount = prepaid->credits, + .giveawayCredits = prepaid->credits, .randomId = prepaid->id, .users = prepaid->quantity, }; @@ -1412,7 +1412,7 @@ void CreateGiveawayBox( return Payments::InvoicePremiumGiftCode{ .currency = option.currency, .storeProduct = option.storeProduct, - .creditsAmount = option.credits, + .giveawayCredits = option.credits, .randomId = UniqueIdFromCreditsOption(option, peer), .amount = option.amount, .users = state->sliderValue.current(), diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index a08aa4f82..a17b2dcfd 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -178,7 +178,7 @@ MTPinputStorePaymentPurpose InvoicePremiumGiftCodeGiveawayToTL( MTPinputStorePaymentPurpose InvoiceCreditsGiveawayToTL( const InvoicePremiumGiftCode &invoice) { - Expects(invoice.creditsAmount.has_value()); + Expects(invoice.giveawayCredits.has_value()); const auto &giveaway = v::get( invoice.purpose); using Flag = MTPDinputStorePaymentStarsGiveaway::Flag; @@ -199,7 +199,7 @@ MTPinputStorePaymentPurpose InvoiceCreditsGiveawayToTL( | (giveaway.additionalPrize.isEmpty() ? Flag() : Flag::f_prize_description)), - MTP_long(*invoice.creditsAmount), + MTP_long(*invoice.giveawayCredits), giveaway.boostPeer->input, MTP_vector_from_range(ranges::views::all( giveaway.additionalChannels @@ -222,7 +222,7 @@ MTPinputStorePaymentPurpose InvoiceCreditsGiveawayToTL( bool IsPremiumForStarsInvoice(const InvoiceId &id) { const auto giftCode = std::get_if(&id.value); return giftCode - && !giftCode->creditsAmount + && !giftCode->giveawayCredits && (giftCode->currency == ::Ui::kCreditsCurrency); } @@ -402,7 +402,7 @@ MTPInputInvoice Form::inputInvoice() const { Api::ConvertOption::SkipLocal))); } const auto &giftCode = v::get(_id.value); - if (giftCode.creditsAmount) { + if (giftCode.giveawayCredits) { return MTP_inputInvoiceStars(InvoiceCreditsGiveawayToTL(giftCode)); } using Flag = MTPDpremiumGiftCodeOption::Flag; diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index 8fa322c72..2e199e359 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -153,7 +153,7 @@ struct InvoicePremiumGiftCode { QString currency; QString storeProduct; - std::optional creditsAmount; + std::optional giveawayCredits; uint64 randomId = 0; uint64 amount = 0; int storeQuantity = 0; From 7b0a156bbadbbc2b4adc0429901d22c5475aab4a Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 4 Mar 2025 21:25:17 +0300 Subject: [PATCH 082/103] Added lottie icon when have no enough info for earn stats. --- Telegram/SourceFiles/data/data_credits_earn.h | 5 ++++- .../channel_statistics/earn/info_channel_earn_list.cpp | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/data/data_credits_earn.h b/Telegram/SourceFiles/data/data_credits_earn.h index 41c6f9a5a..e26e2bebc 100644 --- a/Telegram/SourceFiles/data/data_credits_earn.h +++ b/Telegram/SourceFiles/data/data_credits_earn.h @@ -16,7 +16,10 @@ namespace Data { struct CreditsEarnStatistics final { explicit operator bool() const { - return !!usdRate; + return usdRate + && currentBalance + && availableBalance + && overallRevenue; } Data::StatisticalGraph revenueGraph; StarsAmount currentBalance; diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp index f9dc38da9..b39200c7e 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_web_page.h" #include "data/data_user.h" #include "data/stickers/data_custom_emoji.h" +#include "dialogs/ui/chat_search_empty.h" #include "history/view/controls/history_view_webpage_processor.h" #include "info/bot/starref/info_bot_starref_join_widget.h" #include "info/bot/starref/info_bot_starref_setup_widget.h" @@ -342,6 +343,15 @@ void InnerWidget::load() { void InnerWidget::fill() { const auto container = this; + if (!_state.currencyEarn && !_state.creditsEarn) { + const auto empty = container->add(object_ptr( + container, + Dialogs::SearchEmptyIcon::NoResults, + tr::lng_search_tab_no_results(Ui::Text::Bold))); + empty->setMinimalHeight(st::changePhoneIconSize); + empty->animate(); + return; + } const auto bot = (peerIsUser(_peer->id) && _peer->asUser()->botInfo) ? _peer->asUser() : nullptr; From 95ccc99feef3b43192fb6dbb176b562574b85a4e Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 5 Mar 2025 10:41:44 +0400 Subject: [PATCH 083/103] Hide reply in notification for paid peers. --- Telegram/SourceFiles/window/notifications_manager.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index ce39e6fb1..eee26caec 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -378,6 +378,12 @@ void System::schedule(Data::ItemNotification notification) { registerThread(thread); _whenAlerts[thread].emplace(timing.when, notifyBy); } + if (const auto user = item->history()->peer->asUser()) { + if (user->hasStarsPerMessage() + && !user->messageMoneyRestrictionsKnown()) { + user->updateFull(); + } + } if (Core::App().settings().desktopNotify() && !_manager->skipToast()) { registerThread(thread); @@ -944,7 +950,8 @@ Manager::DisplayOptions Manager::getNotificationOptions( || (!Data::CanSendTexts(peer) && (!topic || !Data::CanSendTexts(topic))) || peer->isBroadcast() - || (peer->slowmodeSecondsLeft() > 0); + || (peer->slowmodeSecondsLeft() > 0) + || (peer->starsPerMessageChecked() > 0); result.spoilerLoginCode = item && !item->out() && peer->isNotificationsUser() From 7840fa6d90d1867c72b89c0c80852dac4021ede0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 5 Mar 2025 12:38:01 +0400 Subject: [PATCH 084/103] Add context menu for gifts in list. --- Telegram/Resources/langs/lang.strings | 2 + .../boosts/giveaway/giveaway.style | 2 + .../peer_gifts/info_peer_gifts_common.cpp | 6 + .../info/peer_gifts/info_peer_gifts_common.h | 6 + .../peer_gifts/info_peer_gifts_widget.cpp | 43 ++++- .../settings/settings_credits_graphics.cpp | 167 +++++++++++------- .../settings/settings_credits_graphics.h | 18 ++ Telegram/SourceFiles/ui/menu_icons.style | 1 + 8 files changed, 181 insertions(+), 64 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index d5abcfbad..e30c963fb 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3486,6 +3486,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_transfer_button_for" = "Transfer for {price}"; "lng_gift_transfer_wear" = "Wear"; "lng_gift_transfer_take_off" = "Take Off"; +"lng_gift_menu_show" = "Show"; +"lng_gift_menu_hide" = "Hide"; "lng_gift_wear_title" = "Wear {name}"; "lng_gift_wear_about" = "and get these benefits:"; "lng_gift_wear_badge_title" = "Radiant Badge"; diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style index 3bf228c9b..afcd0a34e 100644 --- a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style +++ b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style @@ -221,6 +221,8 @@ darkGiftShare: icon {{ "menu/share", groupCallMembersFg }}; darkGiftTransfer: icon {{ "chat/input_replace", groupCallMembersFg }}; darkGiftNftWear: icon {{ "menu/nft_wear", groupCallMembersFg }}; darkGiftNftTakeOff: icon {{ "menu/nft_takeoff", groupCallMembersFg }}; +darkGiftHide: icon {{ "menu/stealth", groupCallMembersFg }}; +darkGiftShow: icon {{ "menu/show_in_chat", groupCallMembersFg }}; darkGiftPalette: TextPalette(defaultTextPalette) { linkFg: mediaviewTextLinkFg; monoFg: groupCallMembersFg; diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp index dfd323656..c48cb6f45 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp @@ -250,6 +250,12 @@ void GiftButton::resizeEvent(QResizeEvent *e) { } } +void GiftButton::contextMenuEvent(QContextMenuEvent *e) { + _contextMenuRequests.fire_copy((e->reason() == QContextMenuEvent::Mouse) + ? e->globalPos() + : QCursor::pos()); +} + void GiftButton::cacheUniqueBackground( not_null unique, int width, diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h index d7d829dab..02c4223b8 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h @@ -126,9 +126,14 @@ public: void setDescriptor(const GiftDescriptor &descriptor, Mode mode); void setGeometry(QRect inner, QMargins extend); + [[nodiscard]] rpl::producer contextMenuRequests() const { + return _contextMenuRequests.events(); + } + private: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; + void contextMenuEvent(QContextMenuEvent *e) override; void cacheUniqueBackground( not_null unique, @@ -141,6 +146,7 @@ private: void unsubscribe(); const not_null _delegate; + rpl::event_stream _contextMenuRequests; QImage _hiddenBgCache; GiftDescriptor _descriptor; Ui::Text::String _text; diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp index 898125cf3..63f48a7a7 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_premium.h" #include "apiwrap.h" #include "data/data_channel.h" +#include "data/data_credits.h" #include "data/data_session.h" #include "data/data_user.h" #include "info/peer_gifts/info_peer_gifts_common.h" @@ -20,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/box_content_divider.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/labels.h" +#include "ui/widgets/popup_menu.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/slide_wrap.h" #include "ui/ui_utility.h" @@ -99,6 +101,7 @@ private: void refreshButtons(); void validateButtons(); void showGift(int index); + void showMenuFor(not_null button, QPoint point); void refreshAbout(); int resizeGetHeight(int width) override; @@ -131,6 +134,8 @@ private: int _visibleFrom = 0; int _visibleTill = 0; + base::unique_qptr _menu; + }; InnerWidget::InnerWidget( @@ -355,7 +360,12 @@ void InnerWidget::validateButtons() { views.push_back(base::take(*unused)); } else { auto button = std::make_unique(this, &_delegate); - button->show(); + const auto raw = button.get(); + raw->contextMenuRequests( + ) | rpl::start_with_next([=](QPoint point) { + showMenuFor(raw, point); + }, raw->lifetime()); + raw->show(); views.push_back({ .button = std::move(button) }); } } @@ -386,6 +396,37 @@ void InnerWidget::validateButtons() { std::swap(_views, views); } +void InnerWidget::showMenuFor(not_null button, QPoint point) { + if (_menu) { + return; + } + const auto index = [&] { + for (const auto &view : _views) { + if (view.button.get() == button) { + return view.index; + } + } + return -1; + }(); + if (index < 0) { + return; + } + + const auto entry = ::Settings::SavedStarGiftEntry( + _peer, + _entries[index].gift); + _menu = base::make_unique_q(this, st::popupMenuWithIcons); + ::Settings::FillSavedStarGiftMenu( + _controller->uiShow(), + _menu.get(), + entry, + ::Settings::SavedStarGiftMenuType::List); + if (_menu->empty()) { + return; + } + _menu->popup(point); +} + void InnerWidget::showGift(int index) { Expects(index >= 0 && index < _entries.size()); diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index d42f40ee8..d680e7ef4 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -189,7 +189,7 @@ void ToggleStarGiftSaved( std::shared_ptr show, Data::SavedStarGiftId savedId, bool save, - Fn done) { + Fn done = nullptr) { using Flag = MTPpayments_SaveStarGift::Flag; const auto api = &show->session().api(); const auto channelGift = savedId.chat(); @@ -197,7 +197,15 @@ void ToggleStarGiftSaved( MTP_flags(save ? Flag(0) : Flag::f_unsave), Api::InputSavedStarGiftId(savedId) )).done([=] { - done(true); + using GiftAction = Data::GiftUpdate::Action; + show->session().data().notifyGiftUpdate({ + .id = savedId, + .action = (save ? GiftAction::Save : GiftAction::Unsave), + }); + + if (const auto onstack = done) { + onstack(true); + } show->showToast((save ? (channelGift ? tr::lng_gift_display_done_channel @@ -206,7 +214,9 @@ void ToggleStarGiftSaved( ? tr::lng_gift_display_done_hide_channel : tr::lng_gift_display_done_hide))(tr::now)); }).fail([=](const MTP::Error &error) { - done(false); + if (const auto onstack = done) { + onstack(false); + } show->showToast(error.type()); }).send(); } @@ -849,26 +859,48 @@ void FillUniqueGiftMenu( std::shared_ptr show, not_null menu, const Data::CreditsHistoryEntry &e, + SavedStarGiftMenuType type, CreditsEntryBoxStyleOverrides st) { - Expects(e.uniqueGift != nullptr); - const auto unique = e.uniqueGift; - const auto local = u"nft/"_q + unique->slug; - const auto url = show->session().createInternalLinkFull(local); - menu->addAction(tr::lng_context_copy_link(tr::now), [=] { - TextUtilities::SetClipboardText({ url }); - show->showToast(tr::lng_channel_public_link_copied(tr::now)); - }, st.link ? st.link : &st::menuIconLink); + if (unique) { + const auto local = u"nft/"_q + unique->slug; + const auto url = show->session().createInternalLinkFull(local); + menu->addAction(tr::lng_context_copy_link(tr::now), [=] { + TextUtilities::SetClipboardText({ url }); + show->showToast(tr::lng_channel_public_link_copied(tr::now)); + }, st.link ? st.link : &st::menuIconLink); - const auto shareBoxSt = st.shareBox; - menu->addAction(tr::lng_chat_link_share(tr::now), [=] { - FastShareLink( - show, - url, - shareBoxSt ? *shareBoxSt : ShareBoxStyleOverrides()); - }, st.share ? st.share : &st::menuIconShare); + const auto shareBoxSt = st.shareBox; + menu->addAction(tr::lng_chat_link_share(tr::now), [=] { + FastShareLink( + show, + url, + shareBoxSt ? *shareBoxSt : ShareBoxStyleOverrides()); + }, st.share ? st.share : &st::menuIconShare); + } const auto savedId = EntryToSavedStarGiftId(&show->session(), e); + const auto giftChannel = savedId.chat(); + const auto canToggleVisibility = savedId + && e.id.isEmpty() + && (e.in || (giftChannel && giftChannel->canManageGifts())) + && !e.giftTransferred + && !e.giftRefunded; + if (canToggleVisibility && type == SavedStarGiftMenuType::List) { + if (e.savedToProfile) { + menu->addAction(tr::lng_gift_menu_hide(tr::now), [=] { + ToggleStarGiftSaved(show, savedId, false); + }, st.hide ? st.hide : &st::menuIconStealth); + } else { + menu->addAction(tr::lng_gift_menu_show(tr::now), [=] { + ToggleStarGiftSaved(show, savedId, true); + }, st.show ? st.show : &st::menuIconShowInChat); + } + } + + if (!unique) { + return; + } const auto transfer = savedId && (savedId.isUser() ? e.in : savedId.chat()->canTransferGifts()) && (unique->starsForTransfer >= 0); @@ -924,6 +956,8 @@ CreditsEntryBoxStyleOverrides DarkCreditsEntryBoxStyle() { .transfer = &st::darkGiftTransfer, .wear = &st::darkGiftNftWear, .takeoff = &st::darkGiftNftTakeOff, + .show = &st::darkGiftShow, + .hide = &st::darkGiftHide, .shareBox = std::make_shared( DarkShareBoxStyle()), .giftWearBox = std::make_shared( @@ -1029,7 +1063,8 @@ void GenericCreditsEntryBox( AddSkip(content, st::defaultVerticalListSkip * 2); AddUniqueCloseButton(box, st, [=](not_null menu) { - FillUniqueGiftMenu(show, menu, e, st); + const auto type = SavedStarGiftMenuType::View; + FillUniqueGiftMenu(show, menu, e, type, st); }); } else if (const auto callback = Ui::PaintPreviewCallback(session, e)) { const auto thumb = content->add(object_ptr>( @@ -1464,21 +1499,12 @@ void GenericCreditsEntryBox( const auto showSection = !e.fromGiftsList; const auto savedId = EntryToSavedStarGiftId(&show->session(), e); const auto done = [=](bool ok) { - if (ok) { - using GiftAction = Data::GiftUpdate::Action; - show->session().data().notifyGiftUpdate({ - .id = savedId, - .action = (save - ? GiftAction::Save - : GiftAction::Unsave), - }); - if (showSection) { - if (const auto window = show->resolveWindow()) { - window->showSection( - std::make_shared( - window->session().user(), - Info::Section::Type::PeerGifts)); - } + if (ok && showSection) { + if (const auto window = show->resolveWindow()) { + window->showSection( + std::make_shared( + window->session().user(), + Info::Section::Type::PeerGifts)); } } if (const auto strong = weak.data()) { @@ -1899,46 +1925,61 @@ void GlobalStarGiftBox( st); } +Data::CreditsHistoryEntry SavedStarGiftEntry( + not_null owner, + const Data::SavedStarGift &data) { + const auto chatGiftPeer = data.manageId.chat(); + return { + .description = data.message, + .date = base::unixtime::parse(data.date), + .credits = StarsAmount(data.info.stars), + .bareMsgId = uint64(data.manageId.userMessageId().bare), + .barePeerId = data.fromId.value, + .bareGiftStickerId = data.info.document->id, + .bareGiftOwnerId = owner->id.value, + .bareActorId = data.fromId.value, + .bareEntryOwnerId = chatGiftPeer ? chatGiftPeer->id.value : 0, + .giftChannelSavedId = data.manageId.chatSavedId(), + .stargiftId = data.info.id, + .uniqueGift = data.info.unique, + .peerType = Data::CreditsHistoryEntry::PeerType::Peer, + .limitedCount = data.info.limitedCount, + .limitedLeft = data.info.limitedLeft, + .starsConverted = int(data.starsConverted), + .starsToUpgrade = int(data.info.starsToUpgrade), + .starsUpgradedBySender = int(data.starsUpgradedBySender), + .converted = false, + .anonymous = data.anonymous, + .stargift = true, + .savedToProfile = !data.hidden, + .fromGiftsList = true, + .canUpgradeGift = data.upgradable, + .in = data.mine, + .gift = true, + }; +} + void SavedStarGiftBox( not_null box, not_null controller, not_null owner, const Data::SavedStarGift &data) { - const auto chatGiftPeer = data.manageId.chat(); Settings::ReceiptCreditsBox( box, controller, - Data::CreditsHistoryEntry{ - .description = data.message, - .date = base::unixtime::parse(data.date), - .credits = StarsAmount(data.info.stars), - .bareMsgId = uint64(data.manageId.userMessageId().bare), - .barePeerId = data.fromId.value, - .bareGiftStickerId = data.info.document->id, - .bareGiftOwnerId = owner->id.value, - .bareActorId = data.fromId.value, - .bareEntryOwnerId = chatGiftPeer ? chatGiftPeer->id.value : 0, - .giftChannelSavedId = data.manageId.chatSavedId(), - .stargiftId = data.info.id, - .uniqueGift = data.info.unique, - .peerType = Data::CreditsHistoryEntry::PeerType::Peer, - .limitedCount = data.info.limitedCount, - .limitedLeft = data.info.limitedLeft, - .starsConverted = int(data.starsConverted), - .starsToUpgrade = int(data.info.starsToUpgrade), - .starsUpgradedBySender = int(data.starsUpgradedBySender), - .converted = false, - .anonymous = data.anonymous, - .stargift = true, - .savedToProfile = !data.hidden, - .fromGiftsList = true, - .canUpgradeGift = data.upgradable, - .in = data.mine, - .gift = true, - }, + SavedStarGiftEntry(owner, data), Data::SubscriptionEntry()); } +void FillSavedStarGiftMenu( + std::shared_ptr show, + not_null menu, + const Data::CreditsHistoryEntry &e, + SavedStarGiftMenuType type, + CreditsEntryBoxStyleOverrides st) { + FillUniqueGiftMenu(show, menu, e, type, st); +} + void StarGiftViewBox( not_null box, not_null controller, diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 59084cb36..0ea66f359 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -52,6 +52,7 @@ namespace Ui { class GenericBox; class RpWidget; class VerticalLayout; +class PopupMenu; } // namespace Ui namespace Settings { @@ -112,6 +113,8 @@ struct CreditsEntryBoxStyleOverrides { const style::icon *transfer = nullptr; const style::icon *wear = nullptr; const style::icon *takeoff = nullptr; + const style::icon *show = nullptr; + const style::icon *hide = nullptr; std::shared_ptr shareBox; std::shared_ptr giftWearBox; }; @@ -149,11 +152,26 @@ void GlobalStarGiftBox( std::shared_ptr show, const Data::StarGift &data, CreditsEntryBoxStyleOverrides st = {}); + +[[nodiscard]] Data::CreditsHistoryEntry SavedStarGiftEntry( + not_null owner, + const Data::SavedStarGift &data); void SavedStarGiftBox( not_null box, not_null controller, not_null owner, const Data::SavedStarGift &data); +enum class SavedStarGiftMenuType { + List, + View, +}; +void FillSavedStarGiftMenu( + std::shared_ptr show, + not_null menu, + const Data::CreditsHistoryEntry &e, + SavedStarGiftMenuType type, + CreditsEntryBoxStyleOverrides st = {}); + void StarGiftViewBox( not_null box, not_null controller, diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index 504065677..f010afa62 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -23,6 +23,7 @@ menuIconStickers: icon {{ "menu/stickers", menuIconColor }}; menuIconEmoji: icon {{ "menu/emoji", menuIconColor }}; menuIconCancel: icon {{ "menu/cancel", menuIconColor }}; menuIconShowInChat: icon {{ "menu/show_in_chat", menuIconColor }}; +menuIconStealth: icon {{ "menu/stealth", menuIconColor }}; menuIconGif: icon {{ "menu/gif", menuIconColor }}; menuIconShowInFolder: icon {{ "menu/show_in_folder", menuIconColor }}; menuIconDownload: icon {{ "menu/download", menuIconColor }}; From 0f74456f3030ef369aa380c981ce4577fcece189 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 5 Mar 2025 14:57:17 +0400 Subject: [PATCH 085/103] Support gifts pinning. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/api/api_premium.cpp | 1 + Telegram/SourceFiles/data/data_credits.h | 2 + Telegram/SourceFiles/data/data_session.h | 2 + Telegram/SourceFiles/data/data_star_gift.h | 1 + .../boosts/giveaway/giveaway.style | 2 + .../peer_gifts/info_peer_gifts_common.cpp | 18 ++++ .../info/peer_gifts/info_peer_gifts_common.h | 2 + .../peer_gifts/info_peer_gifts_widget.cpp | 95 ++++++++++++++++++- Telegram/SourceFiles/main/main_app_config.cpp | 4 + Telegram/SourceFiles/main/main_app_config.h | 2 + .../settings/settings_credits_graphics.cpp | 95 +++++++++++++++++-- .../settings/settings_credits_graphics.h | 2 + Telegram/SourceFiles/ui/effects/credits.style | 1 + 14 files changed, 219 insertions(+), 9 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e30c963fb..7a4a31c83 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3428,6 +3428,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_display_done_channel" = "The gift is now shown in channel's Gifts."; "lng_gift_display_done_hide" = "The gift is now hidden from your profile page."; "lng_gift_display_done_hide_channel" = "The gift is now hidden from channel's Gifts."; +"lng_gift_pinned_done" = "The gift will always be shown on top."; "lng_gift_got_stars#one" = "You got **{count} Star** for this gift."; "lng_gift_got_stars#other" = "You got **{count} Stars** for this gift."; "lng_gift_channel_got#one" = "Channel got **{count} Star** for this gift."; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 313ec1bd1..d7dbcd37f 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -904,6 +904,7 @@ std::optional FromTL( .date = data.vdate().v, .upgradable = data.is_can_upgrade(), .anonymous = data.is_name_hidden(), + .pinned = data.is_pinned_to_top(), .hidden = data.is_unsaved(), .mine = to->isSelf(), }; diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index f569cdd97..af37f4e58 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -70,6 +70,7 @@ struct CreditsHistoryEntry final { uint64 giftChannelSavedId = 0; uint64 stargiftId = 0; std::shared_ptr uniqueGift; + Fn()> pinnedSavedGifts; StarsAmount starrefAmount; int starrefCommission = 0; uint64 starrefRecipientId = 0; @@ -93,6 +94,7 @@ struct CreditsHistoryEntry final { bool giftTransferred : 1 = false; bool giftRefunded : 1 = false; bool giftUpgraded : 1 = false; + bool giftPinned : 1 = false; bool savedToProfile : 1 = false; bool fromGiftsList : 1 = false; bool fromGiftSlug : 1 = false; diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 3e5392173..14f272c6c 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -87,6 +87,8 @@ struct GiftUpdate { Convert, Transfer, Delete, + Pin, + Unpin, }; Data::SavedStarGiftId id; diff --git a/Telegram/SourceFiles/data/data_star_gift.h b/Telegram/SourceFiles/data/data_star_gift.h index e88cae6d1..1b9a8ca06 100644 --- a/Telegram/SourceFiles/data/data_star_gift.h +++ b/Telegram/SourceFiles/data/data_star_gift.h @@ -132,6 +132,7 @@ struct SavedStarGift { TimeId date = 0; bool upgradable = false; bool anonymous = false; + bool pinned = false; bool hidden = false; bool mine = false; }; diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style index afcd0a34e..1558fb259 100644 --- a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style +++ b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style @@ -223,6 +223,8 @@ darkGiftNftWear: icon {{ "menu/nft_wear", groupCallMembersFg }}; darkGiftNftTakeOff: icon {{ "menu/nft_takeoff", groupCallMembersFg }}; darkGiftHide: icon {{ "menu/stealth", groupCallMembersFg }}; darkGiftShow: icon {{ "menu/show_in_chat", groupCallMembersFg }}; +darkGiftPin: icon {{ "menu/pin", groupCallMembersFg }}; +darkGiftUnpin: icon {{ "menu/unpin", groupCallMembersFg }}; darkGiftPalette: TextPalette(defaultTextPalette) { linkFg: mediaviewTextLinkFg; monoFg: groupCallMembersFg; diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp index c48cb6f45..45d424fc0 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp @@ -463,6 +463,24 @@ void GiftButton::paintEvent(QPaintEvent *e) { position.y() - rubberOut, cached); } + + v::match(_descriptor, [](const GiftTypePremium &) { + }, [&](const GiftTypeStars &data) { + if (unique && data.pinned) { + auto hq = PainterHighQualityEnabler(p); + const auto &icon = st::giftBoxPinIcon; + const auto skip = st::giftBoxUserpicSkip; + const auto add = (st::giftBoxUserpicSize - icon.width()) / 2; + p.setPen(Qt::NoPen); + p.setBrush(unique->backdrop.patternColor); + const auto rect = QRect( + QPoint(_extend.left() + skip, _extend.top() + skip), + QSize(icon.width() + 2 * add, icon.height() + 2 * add)); + p.drawEllipse(rect); + icon.paintInCenter(p, rect); + } + }); + if (!_button.isEmpty()) { p.setBrush(unique ? QBrush(QColor(255, 255, 255, .2 * 255)) diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h index 02c4223b8..ddd800b3d 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h @@ -56,7 +56,9 @@ struct GiftTypePremium { struct GiftTypeStars { Data::StarGift info; PeerData *from = nullptr; + TimeId date = 0; bool userpic = false; + bool pinned = false; bool hidden = false; bool mine = false; diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp index 63f48a7a7..376b95c07 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/slide_wrap.h" #include "ui/ui_utility.h" #include "lang/lang_keys.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "mtproto/sender.h" #include "window/window_session_controller.h" @@ -50,7 +51,9 @@ constexpr auto kPerPage = 50; .from = ((gift.anonymous || !gift.fromId) ? nullptr : to->owner().peer(gift.fromId).get()), + .date = gift.date, .userpic = !gift.info.unique, + .pinned = gift.pinned, .hidden = gift.hidden, .mine = to->isSelf(), }; @@ -104,6 +107,9 @@ private: void showMenuFor(not_null button, QPoint point); void refreshAbout(); + void markPinned(std::vector::iterator i); + void markUnpinned(std::vector::iterator i); + int resizeGetHeight(int width) override; const not_null _window; @@ -203,6 +209,13 @@ void InnerWidget::subscribeToUpdates() { view.manageId = {}; } } + } else if (update.action == Action::Pin + || update.action == Action::Unpin) { + if (update.action == Action::Pin) { + markPinned(i); + } else { + markUnpinned(i); + } } else { return; } @@ -210,6 +223,65 @@ void InnerWidget::subscribeToUpdates() { }, lifetime()); } +void InnerWidget::markPinned(std::vector::iterator i) { + const auto index = int(i - begin(_entries)); + + i->gift.pinned = true; + v::match(i->descriptor, [](const GiftTypePremium &) { + }, [&](GiftTypeStars &data) { + data.pinned = true; + }); + if (index) { + std::rotate(begin(_entries), i, i + 1); + } + auto unpin = end(_entries); + const auto session = &_window->session(); + const auto limit = session->appConfig().pinnedGiftsLimit(); + if (limit < _entries.size()) { + const auto j = begin(_entries) + limit; + if (j->gift.pinned) { + unpin = j; + } + } + for (auto &view : _views) { + if (view.index <= index) { + view.index = -1; + view.manageId = {}; + } + } + if (unpin != end(_entries)) { + markUnpinned(unpin); + } +} + +void InnerWidget::markUnpinned(std::vector::iterator i) { + const auto index = int(i - begin(_entries)); + + i->gift.pinned = false; + v::match(i->descriptor, [](const GiftTypePremium &) { + }, [&](GiftTypeStars &data) { + data.pinned = false; + }); + auto after = index + 1; + for (auto j = i + 1; j != end(_entries); ++j) { + if (!j->gift.pinned && j->gift.date <= i->gift.date) { + break; + } + ++after; + } + if (after == _entries.size()) { + _entries.erase(i); + } else if (after > index + 1) { + std::rotate(i, i + 1, begin(_entries) + after); + } + for (auto &view : _views) { + if (view.index >= index) { + view.index = -1; + view.manageId = {}; + } + } +} + void InnerWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { @@ -412,9 +484,30 @@ void InnerWidget::showMenuFor(not_null button, QPoint point) { return; } - const auto entry = ::Settings::SavedStarGiftEntry( + auto entry = ::Settings::SavedStarGiftEntry( _peer, _entries[index].gift); + auto pinnedIds = std::vector(); + for (const auto &entry : _entries) { + if (entry.gift.pinned) { + pinnedIds.push_back(entry.gift.manageId); + } else { + break; + } + } + entry.pinnedSavedGifts = [pinnedIds, peer = _peer] { + auto result = std::vector(); + result.reserve(pinnedIds.size()); + for (const auto &id : pinnedIds) { + result.push_back({ + .bareMsgId = uint64(id.userMessageId().bare), + .bareEntryOwnerId = id.chat() ? id.chat()->id.value : 0, + .giftChannelSavedId = id.chatSavedId(), + .stargift = true, + }); + } + return result; + }; _menu = base::make_unique_q(this, st::popupMenuWithIcons); ::Settings::FillSavedStarGiftMenu( _controller->uiShow(), diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index d1f10b7e8..4326d7559 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -89,6 +89,10 @@ int AppConfig::paidMessageCommission() const { return get(u"stars_paid_message_commission_permille"_q, 850); } +int AppConfig::pinnedGiftsLimit() const { + return get(u"stargifts_pinned_to_top_limit"_q, 6); +} + void AppConfig::refresh(bool force) { if (_requestId || !_api) { if (force) { diff --git a/Telegram/SourceFiles/main/main_app_config.h b/Telegram/SourceFiles/main/main_app_config.h index 58a7da4de..308656bbe 100644 --- a/Telegram/SourceFiles/main/main_app_config.h +++ b/Telegram/SourceFiles/main/main_app_config.h @@ -77,6 +77,8 @@ public: [[nodiscard]] int paidMessageStarsMax() const; [[nodiscard]] int paidMessageCommission() const; + [[nodiscard]] int pinnedGiftsLimit() const; + void refresh(bool force = false); private: diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index d680e7ef4..8e9c87bf3 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -221,6 +221,55 @@ void ToggleStarGiftSaved( }).send(); } +void ToggleStarGiftPinned( + std::shared_ptr show, + Data::SavedStarGiftId savedId, + std::vector already, + bool pinned, + Fn done = nullptr) { + already.erase(ranges::remove(already, savedId), end(already)); + if (pinned) { + already.insert(begin(already), savedId); + const auto limit = show->session().appConfig().pinnedGiftsLimit(); + if (already.size() > limit) { + already.erase(begin(already) + limit, end(already)); + } + } + + auto inputs = QVector(); + inputs.reserve(already.size()); + for (const auto &id : already) { + inputs.push_back(Api::InputSavedStarGiftId(id)); + } + + const auto api = &show->session().api(); + const auto peer = savedId.chat() + ? savedId.chat() + : show->session().user(); + api->request(MTPpayments_ToggleStarGiftsPinnedToTop( + peer->input, + MTP_vector(std::move(inputs)) + )).done([=] { + using GiftAction = Data::GiftUpdate::Action; + show->session().data().notifyGiftUpdate({ + .id = savedId, + .action = (pinned ? GiftAction::Pin : GiftAction::Unpin), + }); + + if (const auto onstack = done) { + onstack(true); + } + if (pinned) { + show->showToast(tr::lng_gift_pinned_done(tr::now)); + } + }).fail([=](const MTP::Error &error) { + if (const auto onstack = done) { + onstack(false); + } + show->showToast(error.type()); + }).send(); +} + void ConfirmConvertStarGift( std::shared_ptr show, rpl::producer confirmText, @@ -861,7 +910,41 @@ void FillUniqueGiftMenu( const Data::CreditsHistoryEntry &e, SavedStarGiftMenuType type, CreditsEntryBoxStyleOverrides st) { + const auto session = &show->session(); + const auto savedId = EntryToSavedStarGiftId(session, e); + const auto giftChannel = savedId.chat(); + const auto canToggle = savedId + && e.id.isEmpty() + && (e.in || (giftChannel && giftChannel->canManageGifts())) + && !e.giftTransferred + && !e.giftRefunded; + const auto unique = e.uniqueGift; + if (unique + && canToggle + && e.savedToProfile + && type == SavedStarGiftMenuType::List) { + const auto already = [session, entries = e.pinnedSavedGifts] { + Expects(entries != nullptr); + + auto list = entries(); + auto result = std::vector(); + result.reserve(list.size()); + for (const auto &entry : list) { + result.push_back(EntryToSavedStarGiftId(session, entry)); + } + return result; + }; + if (e.giftPinned) { + menu->addAction(tr::lng_context_unpin_from_top(tr::now), [=] { + ToggleStarGiftPinned(show, savedId, already(), false); + }, st.unpin ? st.unpin : &st::menuIconUnpin); + } else { + menu->addAction(tr::lng_context_pin_to_top(tr::now), [=] { + ToggleStarGiftPinned(show, savedId, already(), true); + }, st.pin ? st.pin : &st::menuIconPin); + } + } if (unique) { const auto local = u"nft/"_q + unique->slug; const auto url = show->session().createInternalLinkFull(local); @@ -879,14 +962,7 @@ void FillUniqueGiftMenu( }, st.share ? st.share : &st::menuIconShare); } - const auto savedId = EntryToSavedStarGiftId(&show->session(), e); - const auto giftChannel = savedId.chat(); - const auto canToggleVisibility = savedId - && e.id.isEmpty() - && (e.in || (giftChannel && giftChannel->canManageGifts())) - && !e.giftTransferred - && !e.giftRefunded; - if (canToggleVisibility && type == SavedStarGiftMenuType::List) { + if (canToggle && type == SavedStarGiftMenuType::List) { if (e.savedToProfile) { menu->addAction(tr::lng_gift_menu_hide(tr::now), [=] { ToggleStarGiftSaved(show, savedId, false); @@ -958,6 +1034,8 @@ CreditsEntryBoxStyleOverrides DarkCreditsEntryBoxStyle() { .takeoff = &st::darkGiftNftTakeOff, .show = &st::darkGiftShow, .hide = &st::darkGiftHide, + .pin = &st::darkGiftPin, + .unpin = &st::darkGiftUnpin, .shareBox = std::make_shared( DarkShareBoxStyle()), .giftWearBox = std::make_shared( @@ -1951,6 +2029,7 @@ Data::CreditsHistoryEntry SavedStarGiftEntry( .converted = false, .anonymous = data.anonymous, .stargift = true, + .giftPinned = data.pinned, .savedToProfile = !data.hidden, .fromGiftsList = true, .canUpgradeGift = data.upgradable, diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 0ea66f359..8a1ee55bc 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -115,6 +115,8 @@ struct CreditsEntryBoxStyleOverrides { const style::icon *takeoff = nullptr; const style::icon *show = nullptr; const style::icon *hide = nullptr; + const style::icon *pin = nullptr; + const style::icon *unpin = nullptr; std::shared_ptr shareBox; std::shared_ptr giftWearBox; }; diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 8add6b1dc..e9b20d5bb 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -178,6 +178,7 @@ giftListAboutMargin: margins(12px, 24px, 12px, 24px); giftBoxEmojiToggleTop: 7px; giftBoxLimitTop: 28px; giftBoxLockMargins: margins(-2px, 1px, 0px, 0px); +giftBoxPinIcon: icon {{ "dialogs/dialogs_pinned", premiumButtonFg }}; creditsHistoryEntriesList: PeerList(defaultPeerList) { padding: margins( From 940455f7862a11f262ecbd0ef553353ea81d3798 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 5 Mar 2025 17:28:00 +0400 Subject: [PATCH 086/103] Preload gifts for the gift to user layer. --- Telegram/SourceFiles/boxes/star_gift_box.cpp | 62 ++++++++++++++++++- .../peer_gifts/info_peer_gifts_widget.cpp | 5 ++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index 89c53f604..0107b2fbc 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -108,6 +108,7 @@ constexpr auto kSentToastDuration = 3 * crl::time(1000); constexpr auto kSwitchUpgradeCoverInterval = 3 * crl::time(1000); constexpr auto kCrossfadeDuration = crl::time(400); constexpr auto kUpgradeDoneToastDuration = 4 * crl::time(1000); +constexpr auto kGiftsPreloadTimeout = 3 * crl::time(1000); using namespace HistoryView; using namespace Info::PeerGifts; @@ -2193,7 +2194,66 @@ void ChooseStarGiftRecipient( void ShowStarGiftBox( not_null controller, not_null peer) { - controller->show(Box(GiftBox, controller, peer)); + struct Session { + PeerData *peer = nullptr; + bool premiumGiftsReady = false; + bool starsGiftsReady = false; + rpl::lifetime lifetime; + }; + static auto Map = base::flat_map, Session>(); + + const auto session = &controller->session(); + auto i = Map.find(session); + if (i == end(Map)) { + i = Map.emplace(session).first; + session->lifetime().add([=] { Map.remove(session); }); + } else if (i->second.peer == peer) { + return; + } + i->second = Session{ .peer = peer }; + + const auto weak = base::make_weak(controller); + const auto show = [=] { + Map[session] = Session(); + if (const auto strong = weak.get()) { + strong->show(Box(GiftBox, strong, peer)); + } + }; + + base::timer_once( + kGiftsPreloadTimeout + ) | rpl::start_with_next(show, i->second.lifetime); + + const auto user = peer->asUser(); + if (user && !user->isSelf()) { + GiftsPremium( + session, + peer + ) | rpl::start_with_next([=](PremiumGiftsDescriptor &&gifts) { + if (!gifts.list.empty()) { + auto &entry = Map[session]; + entry.premiumGiftsReady = true; + if (entry.starsGiftsReady) { + show(); + } + } + }, i->second.lifetime); + } else { + i->second.premiumGiftsReady = true; + } + + GiftsStars( + session, + peer + ) | rpl::start_with_next([=](std::vector &&gifts) { + if (!gifts.empty()) { + auto &entry = Map[session]; + entry.starsGiftsReady = true; + if (entry.premiumGiftsReady) { + show(); + } + } + }, i->second.lifetime); } void AddUniqueGiftCover( diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp index 376b95c07..63106347d 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp @@ -199,6 +199,8 @@ void InnerWidget::subscribeToUpdates() { } else if (update.action == Action::Save || update.action == Action::Unsave) { i->gift.hidden = (update.action == Action::Unsave); + + const auto unpin = i->gift.hidden && i->gift.pinned; v::match(i->descriptor, [](GiftTypePremium &) { }, [&](GiftTypeStars &data) { data.hidden = i->gift.hidden; @@ -209,6 +211,9 @@ void InnerWidget::subscribeToUpdates() { view.manageId = {}; } } + if (unpin) { + markUnpinned(i); + } } else if (update.action == Action::Pin || update.action == Action::Unpin) { if (update.action == Action::Pin) { From d43a6da62bb77a4b47445f99d6c9563b4e78ea2f Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 5 Mar 2025 18:02:40 +0400 Subject: [PATCH 087/103] Fix sending bot commands in paid chats. --- .../SourceFiles/history/history_widget.cpp | 23 +++++++++++++++++-- Telegram/SourceFiles/history/history_widget.h | 4 ++++ .../view/history_view_replies_section.cpp | 22 +++++++++++++++++- .../view/history_view_replies_section.h | 5 ++++ 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 459b3951c..76353c79f 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -4917,13 +4917,32 @@ void HistoryWidget::mouseReleaseEvent(QMouseEvent *e) { } void HistoryWidget::sendBotCommand(const Bot::SendCommandRequest &request) { -// replyTo != 0 from ReplyKeyboardMarkup, == 0 from command links + sendBotCommand(request, {}); +} + +void HistoryWidget::sendBotCommand( + const Bot::SendCommandRequest &request, + Api::SendOptions options) { + // replyTo != 0 from ReplyKeyboardMarkup, == 0 from command links if (_peer != request.peer.get()) { return; } else if (showSlowmodeError()) { return; } + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + sendBotCommand(request, copy); + }; + const auto checked = checkSendPayment( + 1, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + const auto forMsgId = _keyboard->forMsgId(); const auto lastKeyboardUsed = (forMsgId == request.replyTo.messageId) && (forMsgId == FullMsgId(_peer->id, _history->lastKeyboardId)); @@ -4933,7 +4952,7 @@ void HistoryWidget::sendBotCommand(const Bot::SendCommandRequest &request) { ? request.command : Bot::WrapCommandInChat(_peer, request.command, request.context); - auto message = Api::MessageToSend(prepareSendAction({})); + auto message = Api::MessageToSend(prepareSendAction(options)); message.textWithTags = { toSend, TextWithTags::Tags() }; message.action.replyTo = request.replyTo ? ((!_peer->isUser()/* && (botStatus == 0 || botStatus == 2)*/) diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index e8dbd4f8c..1b0a818bf 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -491,6 +491,10 @@ private: std::shared_ptr bundle, Api::SendOptions options); + void sendBotCommand( + const Bot::SendCommandRequest &request, + Api::SendOptions options); + void uploadFile(const QByteArray &fileContent, SendMediaType type); void itemRemoved(not_null item); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 88f2b44a5..dfd94af4f 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -2724,11 +2724,31 @@ bool RepliesWidget::listIsGoodForAroundPosition( void RepliesWidget::listSendBotCommand( const QString &command, const FullMsgId &context) { + sendBotCommandWithOptions(command, context, {}); +} + +void RepliesWidget::sendBotCommandWithOptions( + const QString &command, + const FullMsgId &context, + Api::SendOptions options) { + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + sendBotCommandWithOptions(command, context, copy); + }; + const auto checked = checkSendPayment( + 1, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + const auto text = Bot::WrapCommandInChat( _history->peer, command, context); - auto message = Api::MessageToSend(prepareSendAction({})); + auto message = Api::MessageToSend(prepareSendAction(options)); message.textWithTags = { text }; session().api().sendMessage(std::move(message)); finishSending(); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index d03f5c834..5d7758b7f 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -319,6 +319,11 @@ private: std::shared_ptr bundle, Api::SendOptions options); + void sendBotCommandWithOptions( + const QString &command, + const FullMsgId &context, + Api::SendOptions options); + bool sendExistingDocument( not_null document, Api::MessageToSend messageToSend, From 0ac88c0cb5dc7c60f4a3b7554e46c25e9cc5311a Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 6 Mar 2025 13:49:49 +0400 Subject: [PATCH 088/103] Show special blank chat for paid messages. --- Telegram/Resources/langs/lang.strings | 3 + .../history/view/history_view_about_view.cpp | 105 +++++++++++++----- .../history/view/history_view_about_view.h | 1 + .../SourceFiles/settings/settings_credits.cpp | 4 + .../SourceFiles/settings/settings_credits.h | 3 + 5 files changed, 89 insertions(+), 27 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 7a4a31c83..68231eeab 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4996,6 +4996,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_send_non_premium_message_toast" = "**{user}** only accepts messages from contacts and {link} subscribers."; "lng_send_non_premium_message_toast_link" = "Telegram Premium"; +"lng_send_charges_stars_text" = "{user} charges {amount} for each message."; +"lng_send_charges_stars_go" = "Buy Stars"; + "lng_exceptions_list_title" = "Exceptions"; "lng_removed_list_title" = "Removed users"; diff --git a/Telegram/SourceFiles/history/view/history_view_about_view.cpp b/Telegram/SourceFiles/history/view/history_view_about_view.cpp index bcf978e52..a76361df5 100644 --- a/Telegram/SourceFiles/history/view/history_view_about_view.cpp +++ b/Telegram/SourceFiles/history/view/history_view_about_view.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "main/main_session.h" #include "settings/business/settings_chat_intro.h" +#include "settings/settings_credits.h" // BuyStarsHandler #include "settings/settings_premium.h" #include "ui/chat/chat_style.h" #include "ui/text/text_utilities.h" @@ -37,14 +38,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/painter.h" #include "window/window_session_controller.h" #include "styles/style_chat.h" +#include "styles/style_credits.h" namespace HistoryView { namespace { -class PremiumRequiredBox final : public ServiceBoxContent { +class EmptyChatLockedBox final + : public ServiceBoxContent + , public base::has_weak_ptr { public: - explicit PremiumRequiredBox(not_null parent); - ~PremiumRequiredBox(); + enum class Type { + PremiumRequired, + StarsCharged, + }; + + EmptyChatLockedBox(not_null parent, Type type); + ~EmptyChatLockedBox(); int width() override; int top() override; @@ -74,6 +83,9 @@ public: private: const not_null _parent; + Settings::BuyStarsHandler _buyStars; + rpl::variable _buyStarsLoading; + Type _type = {}; }; @@ -144,54 +156,63 @@ auto GenerateChatIntro( }; } -PremiumRequiredBox::PremiumRequiredBox(not_null parent) -: _parent(parent) { +EmptyChatLockedBox::EmptyChatLockedBox(not_null parent, Type type) +: _parent(parent) +, _type(type) { } -PremiumRequiredBox::~PremiumRequiredBox() = default; +EmptyChatLockedBox::~EmptyChatLockedBox() = default; -int PremiumRequiredBox::width() { +int EmptyChatLockedBox::width() { return st::premiumRequiredWidth; } -int PremiumRequiredBox::top() { +int EmptyChatLockedBox::top() { return st::msgServiceGiftBoxButtonMargins.top(); } -QSize PremiumRequiredBox::size() { +QSize EmptyChatLockedBox::size() { return { st::msgServicePhotoWidth, st::msgServicePhotoWidth }; } -TextWithEntities PremiumRequiredBox::title() { +TextWithEntities EmptyChatLockedBox::title() { return {}; } -int PremiumRequiredBox::buttonSkip() { +int EmptyChatLockedBox::buttonSkip() { return st::storyMentionButtonSkip; } -rpl::producer PremiumRequiredBox::button() { - return tr::lng_send_non_premium_go(); +rpl::producer EmptyChatLockedBox::button() { + return (_type == Type::PremiumRequired) + ? tr::lng_send_non_premium_go() + : tr::lng_send_charges_stars_go(); } -bool PremiumRequiredBox::buttonMinistars() { +bool EmptyChatLockedBox::buttonMinistars() { return true; } -TextWithEntities PremiumRequiredBox::subtitle() { +TextWithEntities EmptyChatLockedBox::subtitle() { return _parent->data()->notificationText(); } -ClickHandlerPtr PremiumRequiredBox::createViewLink() { - return std::make_shared([=](ClickContext context) { +ClickHandlerPtr EmptyChatLockedBox::createViewLink() { + _buyStarsLoading = _buyStars.loadingValue(); + const auto handler = [=](ClickContext context) { const auto my = context.other.value(); if (const auto controller = my.sessionWindow.get()) { - Settings::ShowPremium(controller, u"require_premium"_q); + if (_type == Type::PremiumRequired) { + Settings::ShowPremium(controller, u"require_premium"_q); + } else if (!_buyStarsLoading.current()) { + _buyStars.handler(controller->uiShow())(); + } } - }); + }; + return std::make_shared(crl::guard(this, handler)); } -void PremiumRequiredBox::draw( +void EmptyChatLockedBox::draw( Painter &p, const PaintContext &context, const QRect &geometry) { @@ -201,20 +222,20 @@ void PremiumRequiredBox::draw( st::premiumRequiredIcon.paintInCenter(p, geometry); } -void PremiumRequiredBox::stickerClearLoopPlayed() { +void EmptyChatLockedBox::stickerClearLoopPlayed() { } -std::unique_ptr PremiumRequiredBox::stickerTakePlayer( +std::unique_ptr EmptyChatLockedBox::stickerTakePlayer( not_null data, const Lottie::ColorReplacements *replacements) { return nullptr; } -bool PremiumRequiredBox::hasHeavyPart() { +bool EmptyChatLockedBox::hasHeavyPart() { return false; } -void PremiumRequiredBox::unloadHeavyPart() { +void EmptyChatLockedBox::unloadHeavyPart() { } } // namespace @@ -259,13 +280,15 @@ bool AboutView::refresh() { if (user && !user->isSelf() && _history->isDisplayedEmpty()) { if (_item) { return false; - //} else if (user->starsPerMessage() > 0) { - // setItem(makeStarsPerMessage(), nullptr); } else if (user->requiresPremiumToWrite() && !user->session().premium()) { setItem(makePremiumRequired(), nullptr); } else if (user->isBlocked()) { setItem(makeBlocked(), nullptr); + } else if (user->businessDetails().intro) { + makeIntro(user); + } else if (const auto stars = user->starsPerMessageChecked()) { + setItem(makeStarsPerMessage(stars), nullptr); } else { makeIntro(user); } @@ -424,7 +447,35 @@ AdminLog::OwnedItem AboutView::makePremiumRequired() { auto result = AdminLog::OwnedItem(_delegate, item); result->overrideMedia(std::make_unique( result.get(), - std::make_unique(result.get()))); + std::make_unique( + result.get(), + EmptyChatLockedBox::Type::PremiumRequired))); + return result; +} + +AdminLog::OwnedItem AboutView::makeStarsPerMessage(int stars) { + const auto item = _history->makeMessage({ + .id = _history->nextNonHistoryEntryId(), + .flags = (MessageFlag::FakeAboutView + | MessageFlag::FakeHistoryItem + | MessageFlag::Local), + .from = _history->peer->id, + }, PreparedServiceText{ tr::lng_send_charges_stars_text( + tr::now, + lt_user, + Ui::Text::Bold(_history->peer->shortName()), + lt_amount, + Ui::Text::IconEmoji( + &st::starIconEmoji + ).append(Ui::Text::Bold(Lang::FormatCountDecimal(stars))), + Ui::Text::RichLangValue), + }); + auto result = AdminLog::OwnedItem(_delegate, item); + result->overrideMedia(std::make_unique( + result.get(), + std::make_unique( + result.get(), + EmptyChatLockedBox::Type::StarsCharged))); return result; } diff --git a/Telegram/SourceFiles/history/view/history_view_about_view.h b/Telegram/SourceFiles/history/view/history_view_about_view.h index 3e78c5c62..57244fb1f 100644 --- a/Telegram/SourceFiles/history/view/history_view_about_view.h +++ b/Telegram/SourceFiles/history/view/history_view_about_view.h @@ -41,6 +41,7 @@ private: DocumentData *document = nullptr, PhotoData *photo = nullptr); [[nodiscard]] AdminLog::OwnedItem makePremiumRequired(); + [[nodiscard]] AdminLog::OwnedItem makeStarsPerMessage(int stars); [[nodiscard]] AdminLog::OwnedItem makeBlocked(); void makeIntro(not_null user); void setItem(AdminLog::OwnedItem item, DocumentData *sticker); diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index f492a6e5e..0e132d82f 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -659,6 +659,10 @@ Type CreditsId() { return Credits::Id(); } +BuyStarsHandler::BuyStarsHandler() = default; + +BuyStarsHandler::~BuyStarsHandler() = default; + Fn BuyStarsHandler::handler( std::shared_ptr<::Main::SessionShow> show, Fn paid) { diff --git a/Telegram/SourceFiles/settings/settings_credits.h b/Telegram/SourceFiles/settings/settings_credits.h index 480ced9e6..27692cd3d 100644 --- a/Telegram/SourceFiles/settings/settings_credits.h +++ b/Telegram/SourceFiles/settings/settings_credits.h @@ -23,6 +23,9 @@ namespace Settings { class BuyStarsHandler final : public base::has_weak_ptr { public: + BuyStarsHandler(); + ~BuyStarsHandler(); + [[nodiscard]] Fn handler( std::shared_ptr<::Main::SessionShow> show, Fn paid = nullptr); From a1e555267e073ff863d642da405d3b5f2420f2be Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 6 Mar 2025 14:02:44 +0400 Subject: [PATCH 089/103] Fix sending invite links to paid. --- .../boxes/peers/add_participants_box.cpp | 233 +++++++++++++++++- 1 file changed, 223 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp index c2b6ff565..4701cac1e 100644 --- a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp @@ -9,10 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_chat_participants.h" #include "api/api_invite_links.h" +#include "api/api_premium.h" #include "boxes/peers/edit_participant_box.h" #include "boxes/peers/edit_peer_type_box.h" #include "boxes/peers/replace_boost_box.h" #include "boxes/max_invite_box.h" +#include "chat_helpers/message_field.h" #include "lang/lang_keys.h" #include "data/data_channel.h" #include "data/data_chat.h" @@ -22,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_peer_values.h" #include "history/history.h" +#include "history/history_item_helpers.h" #include "dialogs/dialogs_indexed_list.h" #include "ui/boxes/confirm_box.h" #include "ui/boxes/show_or_premium_box.h" @@ -52,16 +55,39 @@ constexpr auto kUserpicsLimit = 3; class ForbiddenRow final : public PeerListRow { public: - ForbiddenRow(not_null peer, bool locked); + ForbiddenRow( + not_null peer, + not_null lockSt, + bool locked); PaintRoundImageCallback generatePaintUserpicCallback( bool forceRound) override; + Api::MessageMoneyRestriction restriction() const; + void setRestriction(Api::MessageMoneyRestriction restriction); + + void preloadUserpic() override; + void paintUserpicOverlay( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int outerWidth) override; + + bool refreshLock(); + private: + struct Restriction { + Api::MessageMoneyRestriction value; + RestrictionBadgeCache cache; + }; + const bool _locked = false; + const not_null _lockSt; QImage _disabledFrame; InMemoryKey _userpicKey; int _paletteVersion = 0; + std::shared_ptr _restriction; }; @@ -81,6 +107,9 @@ public: [[nodiscard]] rpl::producer selectedValue() const { return _selected.value(); } + [[nodiscard]] rpl::producer starsToSend() const { + return _starsToSend.value(); + } void send( std::vector> list, @@ -89,10 +118,16 @@ public: private: void appendRow(not_null user); - [[nodiscard]] std::unique_ptr createRow( + [[nodiscard]] std::unique_ptr createRow( not_null user) const; [[nodiscard]] bool canInvite(not_null peer) const; + void send( + std::vector> list, + Ui::ShowPtr show, + Fn close, + Api::SendOptions options); + void setSimpleCover(); void setComplexCover(); @@ -101,8 +136,11 @@ private: const std::vector> &_users; const bool _can = false; rpl::variable _selected; + rpl::variable _starsToSend; bool _sending = false; + rpl::lifetime _paymentCheckLifetime; + }; base::flat_set> GetAlreadyInFromPeer(PeerData *peer) { @@ -256,11 +294,17 @@ Main::Session &InviteForbiddenController::session() const { return _peer->session(); } -ForbiddenRow::ForbiddenRow(not_null peer, bool locked) +ForbiddenRow::ForbiddenRow( + not_null peer, + not_null lockSt, + bool locked) : PeerListRow(peer) -, _locked(locked) { +, _locked(locked) +, _lockSt(lockSt) { if (_locked) { setCustomStatus(tr::lng_invite_status_disabled(tr::now)); + } else { + setRestriction(Api::ResolveMessageMoneyRestrictions(peer, nullptr)); } } @@ -339,6 +383,76 @@ PaintRoundImageCallback ForbiddenRow::generatePaintUserpicCallback( }; } + +Api::MessageMoneyRestriction ForbiddenRow::restriction() const { + return _restriction + ? _restriction->value + : Api::MessageMoneyRestriction(); +} + +void ForbiddenRow::setRestriction(Api::MessageMoneyRestriction restriction) { + if (!restriction || !restriction.starsPerMessage) { + _restriction = nullptr; + return; + } else if (!_restriction) { + _restriction = std::make_unique(); + } + _restriction->value = restriction; +} + +void ForbiddenRow::paintUserpicOverlay( + Painter &p, + const style::PeerListItem &st, + int x, + int y, + int outerWidth) { + if (const auto &r = _restriction) { + PaintRestrictionBadge( + p, + _lockSt, + r->value.starsPerMessage, + r->cache, + x, + y, + outerWidth, + st.photoSize); + } +} + +bool ForbiddenRow::refreshLock() { + if (_locked) { + return false; + } else if (const auto user = peer()->asUser()) { + using Restriction = Api::MessageMoneyRestriction; + auto r = Api::ResolveMessageMoneyRestrictions(user, nullptr); + if (!r || !r.starsPerMessage) { + r = Restriction(); + } + if ((_restriction ? _restriction->value : Restriction()) != r) { + setRestriction(r); + return true; + } + } + return false; +} + +void ForbiddenRow::preloadUserpic() { + PeerListRow::preloadUserpic(); + + const auto peer = this->peer(); + const auto known = Api::ResolveMessageMoneyRestrictions( + peer, + nullptr).known; + if (known) { + return; + } else if (const auto user = peer->asUser()) { + const auto api = &user->session().api(); + api->premium().resolveMessageMoneyRestrictions(user); + } else if (const auto group = peer->asChannel()) { + group->updateFull(); + } +} + void InviteForbiddenController::setSimpleCover() { delegate()->peerListSetTitle( _can ? tr::lng_profile_add_via_link() : tr::lng_via_link_cant()); @@ -435,6 +549,30 @@ void InviteForbiddenController::setComplexCover() { } void InviteForbiddenController::prepare() { + session().api().premium().someMessageMoneyRestrictionsResolved( + ) | rpl::start_with_next([=] { + auto stars = 0; + const auto process = [&](not_null raw) { + const auto row = static_cast(raw.get()); + if (row->refreshLock()) { + delegate()->peerListUpdateRow(raw); + } + if (const auto r = row->restriction()) { + stars += r.starsPerMessage; + } + }; + auto count = delegate()->peerListFullRowsCount(); + for (auto i = 0; i != count; ++i) { + process(delegate()->peerListRowAt(i)); + } + _starsToSend = stars; + + count = delegate()->peerListSearchRowsCount(); + for (auto i = 0; i != count; ++i) { + process(delegate()->peerListSearchRowAt(i)); + } + }, lifetime()); + if (session().premium() || (_forbidden.premiumAllowsInvite.empty() && _forbidden.premiumAllowsWrite.empty())) { @@ -464,6 +602,11 @@ void InviteForbiddenController::rowClicked(not_null row) { const auto checked = row->checked(); delegate()->peerListSetRowChecked(row, !checked); _selected = _selected.current() + (checked ? -1 : 1); + const auto r = static_cast(row.get())->restriction(); + if (r.starsPerMessage) { + _starsToSend = _starsToSend.current() + + (checked ? -r.starsPerMessage : r.starsPerMessage); + } } void InviteForbiddenController::appendRow(not_null user) { @@ -473,6 +616,9 @@ void InviteForbiddenController::appendRow(not_null user) { delegate()->peerListAppendRow(std::move(row)); if (canInvite(user)) { delegate()->peerListSetRowChecked(raw, true); + if (const auto r = raw->restriction()) { + _starsToSend = _starsToSend.current() + r.starsPerMessage; + } } } } @@ -481,7 +627,64 @@ void InviteForbiddenController::send( std::vector> list, Ui::ShowPtr show, Fn close) { - if (_sending || list.empty()) { + send(list, show, close, {}); +} + +void InviteForbiddenController::send( + std::vector> list, + Ui::ShowPtr show, + Fn close, + Api::SendOptions options) { + if (list.empty()) { + return; + } + _paymentCheckLifetime.destroy(); + + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + send(list, show, close, copy); + }; + const auto messagesCount = 1; + const auto alreadyApproved = options.starsApproved; + auto paid = std::vector>(); + auto waiting = base::flat_set>(); + auto totalStars = 0; + for (const auto &peer : list) { + const auto details = ComputePaymentDetails(peer, messagesCount); + if (!details) { + waiting.emplace(peer); + } else if (details->stars > 0) { + totalStars += details->stars; + paid.push_back(peer); + } + } + if (!waiting.empty()) { + session().changes().peerUpdates( + Data::PeerUpdate::Flag::FullInfo + ) | rpl::start_with_next([=](const Data::PeerUpdate &update) { + if (waiting.contains(update.peer)) { + withPaymentApproved(alreadyApproved); + } + }, _paymentCheckLifetime); + + if (!session().credits().loaded()) { + session().credits().loadedValue( + ) | rpl::filter( + rpl::mappers::_1 + ) | rpl::take(1) | rpl::start_with_next([=] { + withPaymentApproved(alreadyApproved); + }, _paymentCheckLifetime); + } + return; + } else if (totalStars > alreadyApproved) { + const auto sessionShow = Main::MakeSessionShow(show, &session()); + ShowSendPaidConfirm(sessionShow, paid, SendPaymentDetails{ + .messages = messagesCount, + .stars = totalStars, + }, [=] { withPaymentApproved(totalStars); }); + return; + } else if (_sending) { return; } _sending = true; @@ -492,12 +695,18 @@ void InviteForbiddenController::send( if (link.isEmpty()) { return false; } + auto full = options; auto &api = _peer->session().api(); - auto options = Api::SendOptions(); for (const auto &to : list) { + auto copy = full; + copy.starsApproved = std::min( + to->starsPerMessageChecked(), + full.starsApproved); + full.starsApproved -= copy.starsApproved; + const auto history = to->owner().history(to); auto message = Api::MessageToSend( - Api::SendAction(history, options)); + Api::SendAction(history, copy)); message.textWithTags = { link }; message.action.clearDraft = false; api.sendMessage(std::move(message)); @@ -542,10 +751,11 @@ void InviteForbiddenController::send( } } -std::unique_ptr InviteForbiddenController::createRow( +std::unique_ptr InviteForbiddenController::createRow( not_null user) const { const auto locked = _can && !canInvite(user); - return std::make_unique(user, locked); + const auto lockSt = &computeListSt().item; + return std::make_unique(user, lockSt, locked); } } // namespace @@ -929,12 +1139,15 @@ bool ChatInviteForbidden( ) | rpl::start_with_next([=](bool has) { box->clearButtons(); if (has) { - box->addButton(tr::lng_via_link_send(), [=] { + const auto send = box->addButton(tr::lng_via_link_send(), [=] { weak->send( box->collectSelectedRows(), box->uiShow(), crl::guard(box, [=] { box->closeBox(); })); }); + send->setText(PaidSendButtonText( + weak->starsToSend(), + tr::lng_via_link_send())); } box->addButton(tr::lng_create_group_skip(), [=] { box->closeBox(); From 0fc8229be1d283ab54276047253236189d4560d1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 6 Mar 2025 18:40:50 +0400 Subject: [PATCH 090/103] Initial new peer information display. --- Telegram/Resources/langs/lang.strings | 14 + Telegram/SourceFiles/data/data_peer.cpp | 66 +++- Telegram/SourceFiles/data/data_peer.h | 9 + .../history/history_inner_widget.cpp | 3 + .../history/view/history_view_about_view.cpp | 110 ++++++- .../history/view/history_view_about_view.h | 2 + .../view/media/history_view_unique_gift.cpp | 292 ++++++++---------- .../view/media/history_view_unique_gift.h | 60 ++++ Telegram/SourceFiles/ui/chat/chat.style | 3 + 9 files changed, 386 insertions(+), 173 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 68231eeab..0748ac25d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3642,6 +3642,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_new_contact_from_request_group" = "{user} is an admin of {name}, a group you requested to join."; "lng_new_contact_about_status" = "This account uses {emoji} as a custom status next to its\nname. Such emoji statuses are available to all\nsubscribers of {link}."; "lng_new_contact_about_status_link" = "Telegram Premium"; +"lng_new_contact_not_contact" = "Not a contact"; +"lng_new_contact_phone_number" = "Phone number"; +"lng_new_contact_registration" = "Registration"; +"lng_new_contact_common_groups" = "Common groups"; +"lng_new_contact_not_official" = "Not an official account"; +"lng_new_contact_updated_name" = "User updated name {when}"; +"lng_new_contact_updated_photo" = "User updated photo {when}"; +"lng_new_contact_updated_now" = "less than an hour ago"; +"lng_new_contact_updated_hours#one" = "{count} hour ago"; +"lng_new_contact_updated_hours#other" = "{count} hours ago"; +"lng_new_contact_updated_days#one" = "{count} day ago"; +"lng_new_contact_updated_days#other" = "{count} days ago"; +"lng_new_contact_updated_months#one" = "{count} month ago"; +"lng_new_contact_updated_months#other" = "{count} months ago"; "lng_from_request_title_channel" = "Response to your join request"; "lng_from_request_title_group" = "Response to your join request"; "lng_from_request_body" = "You received this message because you requested to join {name} on {date}."; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 988c12dd8..5c7536083 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -66,6 +66,28 @@ using UpdateFlag = Data::PeerUpdate::Flag; return session->appConfig().ignoredRestrictionReasons(); } +[[nodiscard]] int ParseRegistrationDate(const QString &text) { + // MM.YYYY + if (text.size() != 7 || text[2] != '.') { + return 0; + } + const auto month = text.mid(0, 2).toInt(); + const auto year = text.mid(3, 4).toInt(); + return (year > 2012 && year < 2100 && month > 0 && month <= 12) + ? (year * 100) + month + : 0; +} + +[[nodiscard]] int RegistrationYear(int date) { + const auto year = date / 100; + return (year > 2012 && year < 2100) ? year : 0; +} + +[[nodiscard]] int RegistrationMonth(int date) { + const auto month = date % 100; + return (month > 0 && month <= 12) ? month : 0; +} + } // namespace namespace Data { @@ -734,7 +756,9 @@ void PeerData::checkFolder(FolderId folderId) { void PeerData::clearBusinessBot() { if (const auto details = _barDetails.get()) { - if (details->requestChatDate || details->paysPerMessage) { + if (details->requestChatDate + || details->paysPerMessage + || !details->phoneCountryCode.isEmpty()) { details->businessBot = nullptr; details->businessBotManageUrl = QString(); } else { @@ -780,12 +804,24 @@ void PeerData::setBarSettings(const MTPPeerSettings &data) { const auto wasPaysPerMessage = paysPerMessage(); if (!data.vbusiness_bot_id() && !data.vrequest_chat_title() - && !data.vcharge_paid_message_stars()) { + && !data.vcharge_paid_message_stars() + && !data.vphone_country() + && !data.vregistration_month() + && !data.vname_change_date() + && !data.vphoto_change_date()) { _barDetails = nullptr; } else if (!_barDetails) { _barDetails = std::make_unique(); } if (_barDetails) { + _barDetails->phoneCountryCode + = qs(data.vphone_country().value_or_empty()); + _barDetails->registrationDate = ParseRegistrationDate( + data.vregistration_month().value_or_empty()); + _barDetails->nameChangeDate + = data.vname_change_date().value_or_empty(); + _barDetails->photoChangeDate + = data.vphoto_change_date().value_or_empty(); _barDetails->requestChatTitle = qs(data.vrequest_chat_title().value_or_empty()); _barDetails->requestChatDate @@ -835,7 +871,9 @@ int PeerData::paysPerMessage() const { void PeerData::clearPaysPerMessage() { if (const auto details = _barDetails.get()) { if (details->paysPerMessage) { - if (details->businessBot || details->requestChatDate) { + if (details->businessBot + || details->requestChatDate + || !details->phoneCountryCode.isEmpty()) { details->paysPerMessage = 0; } else { _barDetails = nullptr; @@ -863,6 +901,28 @@ QString PeerData::businessBotManageUrl() const { return _barDetails ? _barDetails->businessBotManageUrl : QString(); } +QString PeerData::phoneCountryCode() const { + return _barDetails ? _barDetails->phoneCountryCode : QString(); +} + +int PeerData::registrationMonth() const { + return _barDetails + ? RegistrationMonth(_barDetails->registrationDate) + : 0; +} + +int PeerData::registrationYear() const { + return _barDetails ? RegistrationYear(_barDetails->registrationDate) : 0; +} + +TimeId PeerData::nameChangeDate() const { + return _barDetails ? _barDetails->nameChangeDate : 0; +} + +TimeId PeerData::photoChangeDate() const { + return _barDetails ? _barDetails->photoChangeDate : 0; +} + bool PeerData::changeColorIndex( const tl::conditional &cloudColorIndex) { return cloudColorIndex diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index d0b6d687a..21aad4cde 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -173,6 +173,10 @@ inline constexpr bool is_flag_type(PeerBarSetting) { return true; }; using PeerBarSettings = base::flags; struct PeerBarDetails { + QString phoneCountryCode; + int registrationDate = 0; // YYYYMM or 0, YYYY > 2012, MM > 0. + TimeId nameChangeDate = 0; + TimeId photoChangeDate = 0; QString requestChatTitle; TimeId requestChatDate; UserData *businessBot = nullptr; @@ -420,6 +424,11 @@ public: [[nodiscard]] UserData *businessBot() const; [[nodiscard]] QString businessBotManageUrl() const; void clearBusinessBot(); + [[nodiscard]] QString phoneCountryCode() const; + [[nodiscard]] int registrationMonth() const; + [[nodiscard]] int registrationYear() const; + [[nodiscard]] TimeId nameChangeDate() const; + [[nodiscard]] TimeId photoChangeDate() const; enum class TranslationFlag : uchar { Unknown, diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 61ac0fa4e..983ec2839 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -4350,6 +4350,9 @@ void HistoryInner::refreshAboutView(bool force) { if (!info->inited) { session().api().requestFullPeer(user); } + } else if (!user->isContact() + && !user->phoneCountryCode().isEmpty()) { + refresh(); } else if (!historyHeight()) { if (user->starsPerMessage() > 0 || (user->requiresPremiumToWrite() diff --git a/Telegram/SourceFiles/history/view/history_view_about_view.cpp b/Telegram/SourceFiles/history/view/history_view_about_view.cpp index a76361df5..47e24e07a 100644 --- a/Telegram/SourceFiles/history/view/history_view_about_view.cpp +++ b/Telegram/SourceFiles/history/view/history_view_about_view.cpp @@ -14,7 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/premium_preview_box.h" #include "chat_helpers/stickers_lottie.h" #include "core/click_handler_types.h" +#include "core/ui_integration.h" +#include "countries/countries_instance.h" #include "data/business/data_business_common.h" +#include "data/stickers/data_custom_emoji.h" #include "data/data_document.h" #include "data/data_session.h" #include "data/data_user.h" @@ -22,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_service_box.h" #include "history/view/media/history_view_sticker_player_abstract.h" #include "history/view/media/history_view_sticker.h" +#include "history/view/media/history_view_unique_gift.h" #include "history/view/history_view_element.h" #include "history/history.h" #include "history/history_item.h" @@ -43,6 +47,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { namespace { +constexpr auto kLabelOpacity = 0.85; + class EmptyChatLockedBox final : public ServiceBoxContent , public base::has_weak_ptr { @@ -156,6 +162,79 @@ auto GenerateChatIntro( }; } +auto GenerateNewPeerInfo( + not_null parent, + Element *replacing, + not_null user) +-> Fn, + Fn)>)> { + return [=]( + not_null media, + Fn)> push) { + const auto normalFg = [](const PaintContext &context) { + return context.st->msgServiceFg()->c; + }; + const auto fadedFg = [](const PaintContext &context) { + auto result = context.st->msgServiceFg()->c; + result.setAlphaF(result.alphaF() * kLabelOpacity); + return result; + }; + push(std::make_unique( + Ui::Text::Bold(user->name()), + st::newPeerTitleMargin)); + push(std::make_unique( + tr::lng_new_contact_not_contact(tr::now, Ui::Text::WithEntities), + st::newPeerSubtitleMargin, + fadedFg)); + + auto entries = std::vector(); + const auto country = user->phoneCountryCode(); + if (!country.isEmpty()) { + const auto &countries = Countries::Instance(); + const auto name = countries.countryNameByISO2(country); + const auto flag = countries.flagEmojiByISO2(country); + entries.push_back({ + tr::lng_new_contact_phone_number(tr::now), + Ui::Text::Bold(flag + QChar(0xA0) + name), + }); + } + const auto month = user->registrationMonth(); + const auto year = user->registrationYear(); + if (month && year) { + entries.push_back({ + tr::lng_new_contact_registration(tr::now), + Ui::Text::Bold(langMonthOfYearFull(month, year)), + }); + } + + push(std::make_unique( + std::move(entries), + st::newPeerSubtitleMargin, + fadedFg, + normalFg)); + + const auto context = Core::MarkedTextContext{ + .session = &parent->history()->session(), + .customEmojiRepaint = [parent] { parent->repaint(); }, + }; + const auto details = user->botVerifyDetails(); + const auto text = details + ? Data::SingleCustomEmoji( + details->iconId + ).append(' ').append(details->description) + : TextWithEntities().append( + tr::lng_new_contact_not_official(tr::now)); + push(std::make_unique( + text, + st::newPeerSubtitleMargin, + fadedFg, + st::defaultTextStyle, + base::flat_map(), + context)); + }; +} + EmptyChatLockedBox::EmptyChatLockedBox(not_null parent, Type type) : _parent(parent) , _type(type) { @@ -277,7 +356,15 @@ bool AboutView::refresh() { const auto user = _history->peer->asUser(); const auto info = user ? user->botInfo.get() : nullptr; if (!info) { - if (user && !user->isSelf() && _history->isDisplayedEmpty()) { + if (user + && !user->isContact() + && !user->phoneCountryCode().isEmpty()) { + if (_item) { + return false; + } + setItem(makeNewPeerInfo(user), nullptr); + return true; + } else if (user && !user->isSelf() && _history->isDisplayedEmpty()) { if (_item) { return false; } else if (user->requiresPremiumToWrite() @@ -396,6 +483,27 @@ void AboutView::setItem(AdminLog::OwnedItem item, DocumentData *sticker) { toggleStickerRegistered(true); } +AdminLog::OwnedItem AboutView::makeNewPeerInfo(not_null user) { + const auto text = user->name(); + const auto item = _history->makeMessage({ + .id = _history->nextNonHistoryEntryId(), + .flags = (MessageFlag::FakeAboutView + | MessageFlag::FakeHistoryItem + | MessageFlag::Local), + .from = _history->peer->id, + }, PreparedServiceText{ { text }}); + + auto owned = AdminLog::OwnedItem(_delegate, item); + owned->overrideMedia(std::make_unique( + owned.get(), + GenerateNewPeerInfo(owned.get(), _item.get(), user), + HistoryView::MediaGenericDescriptor{ + .service = true, + .hideServiceText = true, + })); + return owned; +} + AdminLog::OwnedItem AboutView::makeAboutVerifyCodes() { return makeAboutSimple( tr::lng_verification_codes_about(tr::now, Ui::Text::RichLangValue)); diff --git a/Telegram/SourceFiles/history/view/history_view_about_view.h b/Telegram/SourceFiles/history/view/history_view_about_view.h index 57244fb1f..2b304d92b 100644 --- a/Telegram/SourceFiles/history/view/history_view_about_view.h +++ b/Telegram/SourceFiles/history/view/history_view_about_view.h @@ -42,6 +42,8 @@ private: PhotoData *photo = nullptr); [[nodiscard]] AdminLog::OwnedItem makePremiumRequired(); [[nodiscard]] AdminLog::OwnedItem makeStarsPerMessage(int stars); + [[nodiscard]] AdminLog::OwnedItem makeNewPeerInfo( + not_null user); [[nodiscard]] AdminLog::OwnedItem makeBlocked(); void makeIntro(not_null user); void setItem(AdminLog::OwnedItem item, DocumentData *sticker); diff --git a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp index 27ca9c545..015ef507a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp @@ -39,60 +39,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { namespace { -class TextPartColored final : public MediaGenericTextPart { -public: - TextPartColored( - TextWithEntities text, - QMargins margins, - QColor color, - const style::TextStyle &st = st::defaultTextStyle, - const base::flat_map &links = {}, - const std::any &context = {}); - -private: - void setupPen( - Painter &p, - not_null owner, - const PaintContext &context) const override; - - QColor _color; - -}; - -class AttributeTable final : public MediaGenericPart { -public: - struct Entry { - QString label; - QString value; - }; - - AttributeTable( - std::vector entries, - QMargins margins, - QColor labelColor); - - void draw( - Painter &p, - not_null owner, - const PaintContext &context, - int outerWidth) const override; - - QSize countOptimalSize() override; - QSize countCurrentSize(int newWidth) override; - -private: - struct Part { - Ui::Text::String label; - Ui::Text::String value; - }; - - std::vector _parts; - QMargins _margins; - QColor _labelColor; - int _valueLeft = 0; - -}; - class ButtonPart final : public MediaGenericPart { public: ButtonPart( @@ -270,116 +216,7 @@ QSize ButtonPart::countCurrentSize(int newWidth) { return optimalSize(); } -TextPartColored::TextPartColored( - TextWithEntities text, - QMargins margins, - QColor color, - const style::TextStyle &st, - const base::flat_map &links, - const std::any &context) -: MediaGenericTextPart(text, margins, st, links, context) -, _color(color) { -} - -void TextPartColored::setupPen( - Painter &p, - not_null owner, - const PaintContext &context) const { - p.setPen(_color); -} - -AttributeTable::AttributeTable( - std::vector entries, - QMargins margins, - QColor labelColor) -: _margins(margins) -, _labelColor(labelColor) { - for (const auto &entry : entries) { - _parts.emplace_back(); - auto &part = _parts.back(); - part.label.setText(st::defaultTextStyle, entry.label); - part.value.setMarkedText( - st::defaultTextStyle, - Ui::Text::Bold(entry.value)); - } -} - -void AttributeTable::draw( - Painter &p, - not_null owner, - const PaintContext &context, - int outerWidth) const { - const auto labelRight = _valueLeft - st::chatUniqueTableSkip; - const auto palette = &context.st->serviceTextPalette(); - auto top = _margins.top(); - const auto paint = [&]( - const Ui::Text::String &text, - int left, - int availableWidth, - style::align align) { - text.draw(p, { - .position = { left, top }, - .outerWidth = outerWidth, - .availableWidth = availableWidth, - .align = align, - .palette = palette, - .spoiler = Ui::Text::DefaultSpoilerCache(), - .now = context.now, - .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), - .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), - .elisionLines = 1, - }); - }; - const auto forLabel = labelRight - _margins.left(); - const auto forValue = width() - _valueLeft - _margins.right(); - const auto white = QColor(255, 255, 255); - for (const auto &part : _parts) { - p.setPen(_labelColor); - paint(part.label, _margins.left(), forLabel, style::al_topright); - p.setPen(white); - paint(part.value, _valueLeft, forValue, style::al_topleft); - top += st::normalFont->height + st::chatUniqueRowSkip; - } -} - -QSize AttributeTable::countOptimalSize() { - auto maxLabel = 0; - auto maxValue = 0; - for (const auto &part : _parts) { - maxLabel = std::max(maxLabel, part.label.maxWidth()); - maxValue = std::max(maxValue, part.value.maxWidth()); - } - const auto skip = st::chatUniqueTableSkip; - const auto row = st::normalFont->height + st::chatUniqueRowSkip; - const auto height = int(_parts.size()) * row - st::chatUniqueRowSkip; - return { - _margins.left() + maxLabel + skip + maxValue + _margins.right(), - _margins.top() + height + _margins.bottom(), - }; -} - -QSize AttributeTable::countCurrentSize(int newWidth) { - const auto skip = st::chatUniqueTableSkip; - const auto width = newWidth - _margins.left() - _margins.right() - skip; - auto maxLabel = 0; - auto maxValue = 0; - for (const auto &part : _parts) { - maxLabel = std::max(maxLabel, part.label.maxWidth()); - maxValue = std::max(maxValue, part.value.maxWidth()); - } - if (width <= 0 || !maxLabel) { - _valueLeft = _margins.left(); - } else if (!maxValue) { - _valueLeft = newWidth - _margins.right(); - } else { - _valueLeft = _margins.left() - + int((int64(maxLabel) * width) / (maxLabel + maxValue)) - + skip; - } - return { newWidth, minHeight() }; -} - -}; // namespace +} // namespace auto GenerateUniqueGiftMedia( not_null parent, @@ -402,7 +239,7 @@ auto GenerateUniqueGiftMedia( push(std::make_unique( std::move(text), margins, - color, + [color](const auto&) { return color; }, st)); }; @@ -447,15 +284,19 @@ auto GenerateUniqueGiftMedia( gift->backdrop.textColor, st::chatUniqueTextPadding); + const auto name = [](const Data::UniqueGiftAttribute &value) { + return Ui::Text::Bold(value.name); + }; auto attributes = std::vector{ - { tr::lng_gift_unique_model(tr::now), gift->model.name }, - { tr::lng_gift_unique_backdrop(tr::now), gift->backdrop.name }, - { tr::lng_gift_unique_symbol(tr::now), gift->pattern.name }, + { tr::lng_gift_unique_model(tr::now), name(gift->model) }, + { tr::lng_gift_unique_backdrop(tr::now), name(gift->backdrop) }, + { tr::lng_gift_unique_symbol(tr::now), name(gift->pattern) }, }; push(std::make_unique( std::move(attributes), st::chatUniqueTextPadding, - gift->backdrop.textColor)); + [c = gift->backdrop.textColor](const auto&) { return c; }, + [](const auto&) { return QColor(255, 255, 255); })); auto link = OpenStarGiftLink(parent->data()); push(std::make_unique( @@ -594,4 +435,117 @@ std::unique_ptr MakeGenericButtonPart( return std::make_unique(text, margins, repaint, link, bg); } +TextPartColored::TextPartColored( + TextWithEntities text, + QMargins margins, + Fn color, + const style::TextStyle &st, + const base::flat_map &links, + const std::any &context) +: MediaGenericTextPart(text, margins, st, links, context) +, _color(std::move(color)) { +} + +void TextPartColored::setupPen( + Painter &p, + not_null owner, + const PaintContext &context) const { + p.setPen(_color(context)); +} + +AttributeTable::AttributeTable( + std::vector entries, + QMargins margins, + Fn labelColor, + Fn valueColor, + const std::any &context) +: _margins(margins) +, _labelColor(std::move(labelColor)) +, _valueColor(std::move(valueColor)) { + for (const auto &entry : entries) { + _parts.emplace_back(); + auto &part = _parts.back(); + part.label.setText(st::defaultTextStyle, entry.label); + part.value.setMarkedText( + st::defaultTextStyle, + entry.value, + kMarkupTextOptions, + context); + } +} + +void AttributeTable::draw( + Painter &p, + not_null owner, + const PaintContext &context, + int outerWidth) const { + const auto labelRight = _valueLeft - st::chatUniqueTableSkip; + const auto palette = &context.st->serviceTextPalette(); + auto top = _margins.top(); + const auto paint = [&]( + const Ui::Text::String &text, + int left, + int availableWidth, + style::align align) { + text.draw(p, { + .position = { left, top }, + .outerWidth = outerWidth, + .availableWidth = availableWidth, + .align = align, + .palette = palette, + .spoiler = Ui::Text::DefaultSpoilerCache(), + .now = context.now, + .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), + .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), + .elisionLines = 1, + }); + }; + const auto forLabel = labelRight - _margins.left(); + const auto forValue = width() - _valueLeft - _margins.right(); + for (const auto &part : _parts) { + p.setPen(_labelColor(context)); + paint(part.label, _margins.left(), forLabel, style::al_topright); + p.setPen(_valueColor(context)); + paint(part.value, _valueLeft, forValue, style::al_topleft); + top += st::normalFont->height + st::chatUniqueRowSkip; + } +} + +QSize AttributeTable::countOptimalSize() { + auto maxLabel = 0; + auto maxValue = 0; + for (const auto &part : _parts) { + maxLabel = std::max(maxLabel, part.label.maxWidth()); + maxValue = std::max(maxValue, part.value.maxWidth()); + } + const auto skip = st::chatUniqueTableSkip; + const auto row = st::normalFont->height + st::chatUniqueRowSkip; + const auto height = int(_parts.size()) * row - st::chatUniqueRowSkip; + return { + _margins.left() + maxLabel + skip + maxValue + _margins.right(), + _margins.top() + height + _margins.bottom(), + }; +} + +QSize AttributeTable::countCurrentSize(int newWidth) { + const auto skip = st::chatUniqueTableSkip; + const auto width = newWidth - _margins.left() - _margins.right() - skip; + auto maxLabel = 0; + auto maxValue = 0; + for (const auto &part : _parts) { + maxLabel = std::max(maxLabel, part.label.maxWidth()); + maxValue = std::max(maxValue, part.value.maxWidth()); + } + if (width <= 0 || !maxLabel) { + _valueLeft = _margins.left(); + } else if (!maxValue) { + _valueLeft = newWidth - _margins.right(); + } else { + _valueLeft = _margins.left() + + int((int64(maxLabel) * width) / (maxLabel + maxValue)) + + skip; + } + return { newWidth, minHeight() }; +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h index 635190af1..269065076 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h +++ b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "history/view/media/history_view_media_generic.h" + class Painter; namespace Data { @@ -55,4 +57,62 @@ class MediaGenericPart; ClickHandlerPtr link, QColor bg = QColor(0, 0, 0, 0)); + +class TextPartColored : public MediaGenericTextPart { +public: + TextPartColored( + TextWithEntities text, + QMargins margins, + Fn color, + const style::TextStyle &st = st::defaultTextStyle, + const base::flat_map &links = {}, + const std::any &context = {}); + +private: + void setupPen( + Painter &p, + not_null owner, + const PaintContext &context) const override; + + Fn _color; + +}; + +class AttributeTable final : public MediaGenericPart { +public: + struct Entry { + QString label; + TextWithEntities value; + }; + + AttributeTable( + std::vector entries, + QMargins margins, + Fn labelColor, + Fn valueColor, + const std::any &context = {}); + + void draw( + Painter &p, + not_null owner, + const PaintContext &context, + int outerWidth) const override; + + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + +private: + struct Part { + Ui::Text::String label; + Ui::Text::String value; + }; + + std::vector _parts; + QMargins _margins; + Fn _labelColor; + Fn _valueColor; + int _valueLeft = 0; + +}; + } // namespace HistoryView diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index fefee68f3..b07eab0be 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1225,3 +1225,6 @@ chatUniqueRowSkip: 4px; chatUniqueButtonPadding: margins(12px, 4px, 12px, 16px); markupWebview: icon {{ "chat/markup_webview", windowFg }}; + +newPeerTitleMargin: margins(11px, 16px, 11px, 6px); +newPeerSubtitleMargin: margins(11px, 0px, 11px, 16px); From 789f3e15846eb4cd64fbe866d3f2f9f52f183e6a Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Mar 2025 14:16:41 +0400 Subject: [PATCH 091/103] Add non-official account info icon. --- .../Resources/icons/chat/mini_info_alert.png | Bin 0 -> 311 bytes .../icons/chat/mini_info_alert@2x.png | Bin 0 -> 578 bytes .../icons/chat/mini_info_alert@3x.png | Bin 0 -> 829 bytes .../history/view/history_view_about_view.cpp | 25 ++++++++++++++++-- Telegram/SourceFiles/ui/chat/chat.style | 9 +++++++ 5 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 Telegram/Resources/icons/chat/mini_info_alert.png create mode 100644 Telegram/Resources/icons/chat/mini_info_alert@2x.png create mode 100644 Telegram/Resources/icons/chat/mini_info_alert@3x.png diff --git a/Telegram/Resources/icons/chat/mini_info_alert.png b/Telegram/Resources/icons/chat/mini_info_alert.png new file mode 100644 index 0000000000000000000000000000000000000000..8c79b97d3ba509237c79ae3dd40945b59b151186 GIT binary patch literal 311 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K$e7J9lkhG>ZH z4LZo%V!)Fv=v~rgH)EmD>=4;!Z94*gy|?>rcQ=x=Yt0mXp+oXbZ;u2jw73f<=dA5D zv||#fV6EKe;tJd?MDrFL8F?}Axk44ofy`glX(f`xTHpSruq6Z zXaU(A42G$TlC0TWzSVF5FO4N|zKE$K5**&0t5#}E(R zw_(3vbe!ne^?#lm^klsk>yElpj@0V(Sl!aXLcIfXWzu9M-W>k4|bNPd}&sfqn-z?hcGfAcIu}0T~db|0nS7~)6?Y}?Y zZ~1JVb1WBfx8FYf^wVR5mI)54{KUc&Hb$8EK29*ZT$+{htb|uqHW582K)GqX{nlW1x43N|5{2ksPn2YneHs` zx1Fl&tZsW62zHE2@L57&sj+VPKc6}3FynlDs^Us#4 z%sYw#&Pm?9arwPBBrvBOj59Zeewmdn% xJfy!~5m^7P`~AoN?*CWTM}N7x=fmT&2jchrS=S0_`aA{2pQo#z%Q~loCIEIz&#~Rs!e|UQKHW#B5e8PNFA~1r=Om-5@;}BVNd24iCr5u{q)kUJjXgkRM><* z@%yO=9ey|=P$;zQL+&5#>2J`vn zCQM;J^x?W+#r-c;wiyWw9$8Fl-!7U_Q(>a=red2W^Rh`gr;EQkz0{E7VJpcxwEAk- zE}P(2i*7ib6K`Q%8?-WjOL$-0`YTUo28kb4Fkd^f#_?|6^k?pkzCu2Z75-OWf7R$( zFlR!8z*dbjB3JUZPd>Rs?pFl2io%j&Bi4tq>yEo7CbrIKD%u$%^2thF`-n)(+unIH zt*xTJ_*q%A+^ps<`#4Xt^rb}huDleT*=OB0{aHQZZP{$Er6Sx1fTlfn@|d2kCs}r^ zYR<_Z9kJ)1EvLouG&n6x=(_TDjlkx63Dei9N9$DZ_qZv0H=I1mWhi?{-ZaqWTHoQ$ z4gu|5?-w@R)7XCd?OjQRD|yE!B*e?sbRNp(RXP8>cx_m)4#SLFuM76h>lKM_&ps;q zD(x0yS!bS@!~Xl*kMEB2xZqN+W7A|R#e1rCrFJHx!tzKFWAm*=)&H!{F5D5K6}jw+ z>&6LlmivmTD!gFUxMhr$#Um- zZY)iXfAe!mxrNM!Ycl=E_kZ#1FVj9&$o`0r!|DUCAu|udwRtz#K}pxs)z4*}Q$iB} DeU4V} literal 0 HcmV?d00001 diff --git a/Telegram/SourceFiles/history/view/history_view_about_view.cpp b/Telegram/SourceFiles/history/view/history_view_about_view.cpp index 47e24e07a..f97e94355 100644 --- a/Telegram/SourceFiles/history/view/history_view_about_view.cpp +++ b/Telegram/SourceFiles/history/view/history_view_about_view.cpp @@ -208,6 +208,26 @@ auto GenerateNewPeerInfo( }); } + if (const auto count = user->commonChatsCount()) { + const auto url = u"internal:common_groups/"_q + + QString::number(user->id.value); + entries.push_back({ + tr::lng_new_contact_common_groups(tr::now), + Ui::Text::Wrapped( + tr::lng_new_contact_groups( + tr::now, + lt_count, + count, + lt_emoji, + TextWithEntities(), + lt_arrow, + Ui::Text::IconEmoji(&st::textMoreIconEmoji), + Ui::Text::Bold), + EntityType::CustomUrl, + url), + }); + } + push(std::make_unique( std::move(entries), st::newPeerSubtitleMargin, @@ -223,8 +243,9 @@ auto GenerateNewPeerInfo( ? Data::SingleCustomEmoji( details->iconId ).append(' ').append(details->description) - : TextWithEntities().append( - tr::lng_new_contact_not_official(tr::now)); + : Ui::Text::IconEmoji( + &st::newPeerNonOfficial + ).append(' ').append(tr::lng_new_contact_not_official(tr::now)); push(std::make_unique( text, st::newPeerSubtitleMargin, diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index b07eab0be..b657a31ad 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -327,6 +327,11 @@ topicButtonArrowSkip: 8px; topicButtonArrowPosition: point(3px, 3px); topicButtonArrow: icon{{ "dialogs/dialogs_topic_arrow", historyReplyIconFg }}; +textMoreIconEmoji: IconEmoji { + icon: topicButtonArrow; + padding: margins(-2px, 5px, 0px, 0px); +} + msgBotKbIconPadding: 4px; msgBotKbUrlIcon: icon {{ "inline_button_url", msgBotKbIconFg }}; msgBotKbSwitchPmIcon: icon {{ "inline_button_switch", msgBotKbIconFg }}; @@ -1228,3 +1233,7 @@ markupWebview: icon {{ "chat/markup_webview", windowFg }}; newPeerTitleMargin: margins(11px, 16px, 11px, 6px); newPeerSubtitleMargin: margins(11px, 0px, 11px, 16px); +newPeerNonOfficial: IconEmoji { + icon: icon{{ "chat/mini_info_alert", windowFg }}; + padding: margins(0px, 2px, 0px, 0px); +} From c9fb97cd7cc5ffc16a4b6bd774fdd7b17bff9634 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Mar 2025 14:17:39 +0400 Subject: [PATCH 092/103] Simplify marked text context logic. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/api/api_chat_filters.cpp | 48 ++++------- .../SourceFiles/boxes/choose_filter_box.cpp | 18 ++--- .../boxes/filters/edit_filter_box.cpp | 16 ++-- .../boxes/filters/edit_filter_chats_list.cpp | 6 +- .../boxes/filters/edit_filter_links.cpp | 12 +-- .../SourceFiles/boxes/gift_credits_box.cpp | 10 +-- .../SourceFiles/boxes/gift_premium_box.cpp | 20 +---- .../boxes/moderate_messages_box.cpp | 5 +- Telegram/SourceFiles/boxes/passcode_box.cpp | 3 +- .../boxes/peers/edit_peer_color_box.cpp | 9 ++- .../boxes/peers/edit_peer_invite_link.cpp | 13 ++- .../boxes/peers/edit_peer_permissions_box.cpp | 5 +- .../boxes/peers/edit_peer_reactions.cpp | 23 +++--- .../SourceFiles/boxes/send_credits_box.cpp | 18 ++--- Telegram/SourceFiles/boxes/send_credits_box.h | 2 +- Telegram/SourceFiles/boxes/send_files_box.cpp | 5 +- Telegram/SourceFiles/boxes/star_gift_box.cpp | 24 ++---- Telegram/SourceFiles/boxes/translate_box.cpp | 10 +-- .../chat_helpers/message_field.cpp | 9 +-- Telegram/SourceFiles/core/ui_integration.cpp | 79 ++++++++++--------- Telegram/SourceFiles/core/ui_integration.h | 16 ++-- .../data/stickers/data_custom_emoji.cpp | 13 +-- .../dialogs/dialogs_inner_widget.cpp | 5 +- .../dialogs/dialogs_search_tags.cpp | 5 +- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 6 +- .../dialogs/ui/dialogs_message_view.cpp | 6 +- .../dialogs/ui/dialogs_topics_view.cpp | 6 +- Telegram/SourceFiles/history/history.cpp | 10 +-- .../history/history_item_components.cpp | 15 ++-- .../history/history_view_top_toast.cpp | 8 +- .../SourceFiles/history/history_widget.cpp | 11 ++- .../history_view_compose_controls.cpp | 11 ++- .../controls/history_view_forward_panel.cpp | 6 +- .../history/view/history_view_about_view.cpp | 6 +- .../view/history_view_contact_status.cpp | 14 ++-- .../view/history_view_contact_status.h | 2 +- .../history/view/history_view_element.cpp | 6 +- .../history/view/history_view_message.cpp | 11 ++- .../history/view/history_view_pinned_bar.cpp | 6 +- .../history/view/history_view_reply.cpp | 11 ++- .../history/view/media/history_view_game.cpp | 12 +-- .../history/view/media/history_view_media.cpp | 20 +++-- .../view/media/history_view_media_generic.cpp | 2 +- .../view/media/history_view_media_generic.h | 2 +- .../history/view/media/history_view_poll.cpp | 18 ++--- .../view/media/history_view_service_box.cpp | 12 +-- .../view/media/history_view_unique_gift.cpp | 4 +- .../view/media/history_view_unique_gift.h | 4 +- .../view/media/history_view_web_page.cpp | 25 +++--- .../reactions/history_view_reactions_list.cpp | 2 +- .../reactions/history_view_reactions_tabs.cpp | 2 +- .../bot/starref/info_bot_starref_common.cpp | 8 +- .../earn/info_channel_earn_list.cpp | 22 ++---- .../peer_gifts/info_peer_gifts_common.cpp | 7 +- .../info/peer_gifts/info_peer_gifts_common.h | 4 +- .../info/profile/info_profile_actions.cpp | 12 +-- .../info_statistics_list_controllers.cpp | 30 +++---- .../info_statistics_recent_message.cpp | 12 +-- .../inline_bots/bot_attach_web_view.cpp | 18 +---- Telegram/SourceFiles/intro/intro_step.cpp | 8 +- .../media_stories_caption_full_view.cpp | 5 +- .../media/stories/media_stories_header.cpp | 5 +- .../stories/media_stories_repost_view.cpp | 9 +-- .../media/view/media_view_overlay_widget.cpp | 6 +- Telegram/SourceFiles/menu/menu_sponsored.cpp | 9 +-- .../SourceFiles/overview/overview_layout.cpp | 8 +- .../payments/payments_reaction_process.cpp | 7 +- .../payments/ui/payments_reaction_box.h | 2 +- .../settings/business/settings_chat_links.cpp | 6 +- .../settings_cloud_password_common.cpp | 5 +- .../settings/settings_business.cpp | 9 +-- .../settings/settings_credits_graphics.cpp | 51 +++++------- .../SourceFiles/settings/settings_folders.cpp | 6 +- .../SourceFiles/settings/settings_premium.cpp | 10 +-- .../ui/boxes/collectible_info_box.cpp | 4 +- .../ui/boxes/collectible_info_box.h | 2 +- .../ui/boxes/edit_invite_link_session.cpp | 10 +-- .../SourceFiles/ui/chat/chats_filter_tag.cpp | 5 +- .../SourceFiles/ui/chat/chats_filter_tag.h | 7 +- Telegram/SourceFiles/ui/chat/message_bar.h | 2 +- .../ui/chat/sponsored_message_bar.cpp | 8 +- .../ui/controls/filter_link_header.cpp | 39 +++++---- .../ui/controls/filter_link_header.h | 4 +- .../SourceFiles/ui/controls/tabbed_search.cpp | 2 +- .../controls/who_reacted_context_action.cpp | 6 +- Telegram/SourceFiles/ui/unread_badge.cpp | 2 +- .../ui/widgets/chat_filters_tabs_slider.cpp | 2 +- .../ui/widgets/chat_filters_tabs_slider.h | 2 +- .../ui/widgets/chat_filters_tabs_strip.cpp | 11 +-- .../ui/widgets/discrete_sliders.cpp | 9 ++- .../SourceFiles/ui/widgets/discrete_sliders.h | 9 ++- .../ui/widgets/label_with_custom_emoji.cpp | 7 +- .../ui/widgets/label_with_custom_emoji.h | 12 +-- .../window/notifications_manager_default.cpp | 12 +-- .../window/window_filters_menu.cpp | 12 +-- .../window/window_session_controller.cpp | 8 +- Telegram/lib_ui | 2 +- 98 files changed, 441 insertions(+), 609 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0748ac25d..5f59ce1b3 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3646,6 +3646,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_new_contact_phone_number" = "Phone number"; "lng_new_contact_registration" = "Registration"; "lng_new_contact_common_groups" = "Common groups"; +"lng_new_contact_groups#one" = "{count} group {emoji} {arrow}"; +"lng_new_contact_groups#other" = "{count} groups {emoji} {arrow}"; "lng_new_contact_not_official" = "Not an official account"; "lng_new_contact_updated_name" = "User updated name {when}"; "lng_new_contact_updated_photo" = "User updated photo {when}"; diff --git a/Telegram/SourceFiles/api/api_chat_filters.cpp b/Telegram/SourceFiles/api/api_chat_filters.cpp index d7a1636b3..55bbabbc5 100644 --- a/Telegram/SourceFiles/api/api_chat_filters.cpp +++ b/Telegram/SourceFiles/api/api_chat_filters.cpp @@ -149,18 +149,14 @@ void InitFilterLinkHeader( iconEmoji ).value_or(Ui::FilterIcon::Custom)).active; const auto isStatic = title.isStatic; - const auto makeContext = [=](Fn repaint) { - return Core::MarkedTextContext{ - .session = &box->peerListUiShow()->session(), - .customEmojiRepaint = std::move(repaint), - .customEmojiLoopLimit = isStatic ? -1 : 0, - }; - }; auto header = Ui::MakeFilterLinkHeader(box, { .type = type, .title = TitleText(type)(tr::now), .about = AboutText(type, title.text), - .makeAboutContext = makeContext, + .aboutContext = Core::TextContext({ + .session = &box->peerListUiShow()->session(), + .customEmojiLoopLimit = isStatic ? -1 : 0, + }), .folderTitle = title.text, .folderIcon = icon, .badge = (type == Ui::FilterLinkHeaderType::AddingChats @@ -560,16 +556,12 @@ void ShowImportToast( text.append('\n').append(phrase(tr::now, lt_count, added)); } const auto isStatic = title.isStatic; - const auto makeContext = [=](not_null widget) { - return Core::MarkedTextContext{ - .session = &strong->session(), - .customEmojiRepaint = [=] { widget->update(); }, - .customEmojiLoopLimit = isStatic ? -1 : 0, - }; - }; strong->showToast({ .text = std::move(text), - .textContext = makeContext, + .textContext = Core::TextContext({ + .session = &strong->session(), + .customEmojiLoopLimit = isStatic ? -1 : 0, + }) }); } @@ -640,18 +632,14 @@ void ProcessFilterInvite( raw->setRealContentHeight(box->heightValue()); const auto isStatic = title.isStatic; - const auto makeContext = [=](Fn update) { - return Core::MarkedTextContext{ - .session = &strong->session(), - .customEmojiRepaint = update, - .customEmojiLoopLimit = isStatic ? -1 : 0, - }; - }; auto owned = Ui::FilterLinkProcessButton( box, type, title.text, - makeContext, + Core::TextContext({ + .session = &strong->session(), + .customEmojiLoopLimit = isStatic ? -1 : 0, + }), std::move(badge)); const auto button = owned.data(); @@ -873,18 +861,14 @@ void ProcessFilterRemove( }, type, title, iconEmoji, rpl::single(0), horizontalFilters); const auto isStatic = title.isStatic; - const auto makeContext = [=](Fn update) { - return Core::MarkedTextContext{ - .session = &strong->session(), - .customEmojiRepaint = update, - .customEmojiLoopLimit = isStatic ? -1 : 0, - }; - }; auto owned = Ui::FilterLinkProcessButton( box, type, title.text, - makeContext, + Core::TextContext({ + .session = &strong->session(), + .customEmojiLoopLimit = isStatic ? -1 : 0, + }), std::move(badge)); const auto button = owned.data(); diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp index 7448332d3..93164126b 100644 --- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/choose_filter_box.cpp @@ -172,13 +172,6 @@ void ChangeFilterById( const auto account = not_null(&history->session().account()); if (const auto controller = Core::App().windowFor(account)) { const auto isStatic = name.isStatic; - const auto textContext = [=](not_null widget) { - return Core::MarkedTextContext{ - .session = &history->session(), - .customEmojiRepaint = [=] { widget->update(); }, - .customEmojiLoopLimit = isStatic ? -1 : 0, - }; - }; controller->showToast({ .text = (add ? tr::lng_filters_toast_add @@ -189,7 +182,10 @@ void ChangeFilterById( lt_folder, Ui::Text::Wrapped(name.text, EntityType::Bold), Ui::Text::WithEntities), - .textContext = textContext, + .textContext = Core::TextContext({ + .session = &history->session(), + .customEmojiLoopLimit = isStatic ? -1 : 0, + }), }); } }).fail([=](const MTP::Error &error) { @@ -297,12 +293,10 @@ void FillChooseFilterMenu( std::move(callback)), contains ? &st::mediaPlayerMenuCheck : nullptr, contains ? &st::mediaPlayerMenuCheck : nullptr); - const auto context = Core::MarkedTextContext{ + item->setMarkedText(title.text, QString(), Core::TextContext({ .session = &history->session(), - .customEmojiRepaint = [raw = item.get()] { raw->update(); }, .customEmojiLoopLimit = title.isStatic ? -1 : 0, - }; - item->setMarkedText(title.text, QString(), context); + })); item->setIcon(Icon(showColors ? filter : filter.withColorIndex({}))); const auto action = menu->addAction(std::move(item)); diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index f9a05a557..be4531aec 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -441,13 +441,10 @@ void EditFilterBox( using namespace Window; return window->isGifPausedAtLeastFor(GifPauseReason::Layer); }; - name->setCustomTextContext([=](Fn repaint) { - return std::any(Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = std::move(repaint), - .customEmojiLoopLimit = value ? -1 : 0, - }); - }, [paused] { + name->setCustomTextContext(Core::TextContext({ + .session = session, + .customEmojiLoopLimit = value ? -1 : 0, + }), [paused] { return On(PowerSaving::kEmojiChat) || paused(); }, [paused] { return On(PowerSaving::kChatSpoiler) || paused(); @@ -609,10 +606,7 @@ void EditFilterBox( float64 alpha = 1.; }; const auto tag = preview->lifetime().make_state(); - tag->context.textContext = Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = [] {}, - }; + tag->context.textContext = Core::TextContext({ .session = session }); preview->paintRequest() | rpl::start_with_next([=] { auto p = QPainter(preview); p.setOpacity(tag->alpha); diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp index 2ed72aa25..a200bcecb 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp @@ -163,10 +163,10 @@ ExceptionRow::ExceptionRow( st::defaultTextStyle, filters, kMarkupTextOptions, - Core::MarkedTextContext{ + Core::TextContext({ .session = &history->session(), - .customEmojiRepaint = repaint, - }); + .repaint = repaint, + })); } else if (peer()->isSelf()) { setCustomStatus(tr::lng_saved_forward_here(tr::now)); } diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp index 61bbd85a0..90dd083bd 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp @@ -537,13 +537,6 @@ void LinkController::addHeader(not_null container) { verticalLayout->add(std::move(icon.widget)); const auto isStatic = _filterTitle.isStatic; - const auto makeContext = [=](Fn update) { - return Core::MarkedTextContext{ - .session = &_window->session(), - .customEmojiRepaint = update, - .customEmojiLoopLimit = isStatic ? -1 : 0, - }; - }; verticalLayout->add( object_ptr>( verticalLayout, @@ -559,7 +552,10 @@ void LinkController::addHeader(not_null container) { Ui::Text::WithEntities)), st::settingsFilterDividerLabel, st::defaultPopupMenu, - makeContext)), + Core::TextContext({ + .session = &_window->session(), + .customEmojiLoopLimit = isStatic ? -1 : 0, + }))), st::filterLinkDividerLabelPadding); verticalLayout->geometryValue( diff --git a/Telegram/SourceFiles/boxes/gift_credits_box.cpp b/Telegram/SourceFiles/boxes/gift_credits_box.cpp index 80e859eb1..761b387e5 100644 --- a/Telegram/SourceFiles/boxes/gift_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_credits_box.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_credits.h" #include "boxes/peer_list_controllers.h" +#include "core/ui_integration.h" // TextContext. #include "data/data_peer.h" #include "data/data_session.h" #include "data/data_user.h" @@ -67,14 +68,9 @@ void GiftCreditsBox( 2.); { Ui::AddSkip(content); - const auto arrow = Ui::Text::SingleCustomEmoji( - peer->owner().customEmojiManager().registerInternalEmoji( - st::topicButtonArrow, - st::channelEarnLearnArrowMargins, - true)); auto link = tr::lng_credits_box_history_entry_gift_about_link( lt_emoji, - rpl::single(arrow), + rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)), Ui::Text::RichLangValue ) | rpl::map([](TextWithEntities text) { return Ui::Text::Link( @@ -92,7 +88,7 @@ void GiftCreditsBox( lt_link, std::move(link), Ui::Text::RichLangValue), - { .session = &peer->session() }, + Core::TextContext({ .session = &peer->session() }), st::creditsBoxAbout)), st::boxRowPadding); } diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 1d12e7f81..691821c16 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -517,13 +517,13 @@ not_null AddTableRow( not_null table, rpl::producer label, rpl::producer value, - const Fn)> &makeContext = nullptr) { + const Ui::Text::MarkedContext &context = {}) { auto widget = object_ptr( table, std::move(value), table->st().defaultValue, st::defaultPopupMenu, - std::move(makeContext)); + context); const auto result = widget.data(); AddTableRow( table, @@ -1527,12 +1527,6 @@ void AddStarGiftTable( : nullptr; const auto date = base::unixtime::parse(original.date).date(); const auto dateText = TextWithEntities{ langDayOfMonth(date) }; - const auto makeContext = [=](Fn update) { - return Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = std::move(update), - }; - }; auto label = object_ptr( table, (from @@ -1574,7 +1568,7 @@ void AddStarGiftTable( ? *st.tableValueMessage : st::giveawayGiftMessage), st::defaultPopupMenu, - makeContext); + Core::TextContext({ .session = session })); const auto showBoxLink = [=](not_null peer) { return std::make_shared([=] { show->showBox(PrepareShortInfoBox(peer, show)); @@ -1592,12 +1586,6 @@ void AddStarGiftTable( st::giveawayGiftCodeValueMargin); } } else if (!entry.description.empty()) { - const auto makeContext = [=](Fn update) { - return Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = std::move(update), - }; - }; auto label = object_ptr( table, rpl::single(entry.description), @@ -1605,7 +1593,7 @@ void AddStarGiftTable( ? *st.tableValueMessage : st::giveawayGiftMessage), st::defaultPopupMenu, - makeContext); + Core::TextContext({ .session = session })); label->setSelectable(true); table->addRow( nullptr, diff --git a/Telegram/SourceFiles/boxes/moderate_messages_box.cpp b/Telegram/SourceFiles/boxes/moderate_messages_box.cpp index 8007b9383..d278d2681 100644 --- a/Telegram/SourceFiles/boxes/moderate_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/moderate_messages_box.cpp @@ -453,10 +453,7 @@ void CreateModerateMessagesBox( ) | rpl::start_with_next([=](const TextWithEntities &text) { raw->setMarkedText( Ui::Text::Link(text, u"internal:"_q), - Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = [=] { raw->update(); }, - }); + Core::TextContext({ .session = session })); }, label->lifetime()); Ui::AddSkip(inner); diff --git a/Telegram/SourceFiles/boxes/passcode_box.cpp b/Telegram/SourceFiles/boxes/passcode_box.cpp index 308384e16..96d5f2d33 100644 --- a/Telegram/SourceFiles/boxes/passcode_box.cpp +++ b/Telegram/SourceFiles/boxes/passcode_box.cpp @@ -1154,8 +1154,7 @@ RecoverBox::RecoverBox( rpl::single(Ui::Text::WrapEmailPattern(pattern)), Ui::Text::WithEntities), st::termsContent, - st::defaultPopupMenu, - [=](Fn update) { return CommonTextContext{ std::move(update) }; }) + st::defaultPopupMenu) , _closeParent(std::move(closeParent)) { _patternLabel->setAttribute(Qt::WA_TransparentForMouseEvents); if (_cloudFields.pendingResetDate != 0 || !session) { diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index 73b3004f0..c457ce579 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/background_box.h" #include "boxes/stickers_box.h" #include "chat_helpers/compose/compose_show.h" -#include "core/ui_integration.h" // Core::MarkedTextContext. +#include "core/ui_integration.h" // TextContext #include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_stickers.h" #include "data/data_changes.h" @@ -165,7 +165,7 @@ private: const uint32 _level; const TextWithEntities _icon; - const Core::MarkedTextContext _context; + const Ui::Text::MarkedContext _context; Ui::Text::String _text; bool _minimal = false; @@ -466,7 +466,10 @@ LevelBadge::LevelBadge( st::settingsLevelBadgeLock, QMargins(0, st::settingsLevelBadgeLockSkip, 0, 0), false))) -, _context({ .session = session }) { +, _context(Core::TextContext({ + .session = session, + .repaint = [this] { update(); }, +})) { updateText(); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp index 38eb775f6..70ad41acb 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peer_list_controllers.h" #include "boxes/share_box.h" #include "core/application.h" -#include "core/ui_integration.h" // Core::MarkedTextContext. +#include "core/ui_integration.h" // TextContext #include "data/components/credits.h" #include "data/data_changes.h" #include "data/data_channel.h" @@ -740,10 +740,10 @@ void Controller::setupAboveJoinedWidget() { { QString::number(current.subscription.credits) }, Ui::Text::WithEntities), kMarkupTextOptions, - Core::MarkedTextContext{ + Core::TextContext({ .session = &session(), - .customEmojiRepaint = [=] { widget->update(); }, - }); + .repaint = [=] { widget->update(); }, + })); auto &lifetime = widget->lifetime(); const auto rateValue = lifetime.make_state>( session().credits().rateValue(_peer)); @@ -994,10 +994,7 @@ void Controller::rowClicked(not_null row) { lt_cost, { QString::number(data.subscription.credits) }, Ui::Text::WithEntities), - Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = [=] { subtitle1->update(); }, - }); + Core::TextContext({ .session = session })); const auto subtitle2 = box->addRow( object_ptr>( box, diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index 486383f0b..6e824ac35 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -890,11 +890,10 @@ void AddBoostsUnrestrictLabels( manager->registerInternalEmoji( st::boostsMessageIcon, st::boostsMessageIconPadding)); - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = session, - .customEmojiRepaint = [] {}, .customEmojiLoopLimit = 1, - }; + }); for (auto i = 0; i != kBoostsUnrestrictValues; ++i) { const auto label = Ui::CreateChild( labels, diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp index 96ed8c949..5313a2ae3 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp @@ -363,12 +363,15 @@ object_ptr AddReactionsSelector( const auto customEmojiPaused = [controller = args.controller] { return controller->isGifPausedAtLeastFor(PauseReason::Layer); }; - auto factory = [=](QStringView data, Fn update) - -> std::unique_ptr { + auto context = Core::TextContext({ + .session = session, + }); + context.customEmojiFactory = [=]( + QStringView data, + const Ui::Text::MarkedContext &context + ) -> std::unique_ptr { const auto id = Data::ParseCustomEmojiData(data); - auto result = owner->customEmojiManager().create( - data, - std::move(update)); + auto result = Ui::Text::MakeCustomEmoji(data, context); if (state->unifiedFactoryOwner->lookupReactionId(id).custom()) { return std::make_unique( std::move(result), @@ -377,12 +380,10 @@ object_ptr AddReactionsSelector( using namespace Ui::Text; return std::make_unique(std::move(result)); }; - raw->setCustomTextContext([=](Fn repaint) { - return std::any(Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = std::move(repaint), - }); - }, customEmojiPaused, customEmojiPaused, std::move(factory)); + raw->setCustomTextContext( + std::move(context), + customEmojiPaused, + customEmojiPaused); const auto callback = args.callback; const auto isCustom = [=](DocumentId id) { diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index 8091e7ea4..2a4e4f242 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_credits.h" #include "apiwrap.h" -#include "core/ui_integration.h" // Core::MarkedTextContext. +#include "core/ui_integration.h" // TextContext #include "data/components/credits.h" #include "data/data_credits.h" #include "data/data_photo.h" @@ -519,21 +519,20 @@ TextWithEntities CreditsEmojiSmall(not_null session) { not_null SetButtonMarkedLabel( not_null button, rpl::producer text, - Fn update)> context, + Text::MarkedContext context, const style::FlatLabel &st, const style::color *textFg) { const auto buttonLabel = Ui::CreateChild( button, rpl::single(QString()), st); + context.repaint = [=] { buttonLabel->update(); }; rpl::duplicate( text ) | rpl::filter([=](const TextWithEntities &text) { return !text.text.isEmpty(); }) | rpl::start_with_next([=](const TextWithEntities &text) { - buttonLabel->setMarkedText( - text, - context([=] { buttonLabel->update(); })); + buttonLabel->setMarkedText(text, context); }, buttonLabel->lifetime()); if (textFg) { buttonLabel->setTextColorOverride((*textFg)->c); @@ -562,12 +561,9 @@ not_null SetButtonMarkedLabel( not_null session, const style::FlatLabel &st, const style::color *textFg) { - return SetButtonMarkedLabel(button, text, [=](Fn update) { - return Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = update, - }; - }, st, textFg); + return SetButtonMarkedLabel(button, text, Core::TextContext({ + .session = session, + }), st, textFg); } void SendStarsForm( diff --git a/Telegram/SourceFiles/boxes/send_credits_box.h b/Telegram/SourceFiles/boxes/send_credits_box.h index e51dc39b3..6dcaef1f8 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.h +++ b/Telegram/SourceFiles/boxes/send_credits_box.h @@ -41,7 +41,7 @@ void SendCreditsBox( not_null SetButtonMarkedLabel( not_null button, rpl::producer text, - Fn update)> context, + Text::MarkedContext context, const style::FlatLabel &st, const style::color *textFg = nullptr); diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index a269f83bd..e9d04211d 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -859,10 +859,9 @@ void SendFilesBox::refreshPriceTag() { QString(), st::paidTagLabel); std::move(text) | rpl::start_with_next([=](TextWithEntities &&text) { - label->setMarkedText(text, Core::MarkedTextContext{ + label->setMarkedText(text, Core::TextContext({ .session = session, - .customEmojiRepaint = [=] { label->update(); }, - }); + })); }, label->lifetime()); label->show(); label->sizeValue() | rpl::start_with_next([=](QSize size) { diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index 0107b2fbc..bc15e9059 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -231,7 +231,7 @@ auto GenerateGiftMedia( TextWithEntities text, QMargins margins = {}, const base::flat_map &links = {}, - const std::any &context = {}) { + Ui::Text::MarkedContext context = {}) { if (text.empty()) { return; } @@ -240,7 +240,7 @@ auto GenerateGiftMedia( margins, st::defaultTextStyle, links, - context)); + std::move(context))); }; const auto sticker = [=] { @@ -310,10 +310,10 @@ auto GenerateGiftMedia( auto description = data.text.empty() ? std::move(textFallback) : data.text; - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &parent->history()->session(), - .customEmojiRepaint = [parent] { parent->repaint(); }, - }; + .repaint = [parent] { parent->repaint(); }, + }); pushText( std::move(title), st::giftBoxPreviewTitlePadding, @@ -759,15 +759,11 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { } auto &manager = session->data().customEmojiManager(); auto result = Text::String(); - const auto context = Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = [] {}, - }; result.setMarkedText( st::semiboldTextStyle, manager.creditsEmoji().append(QString::number(price)), kMarkupTextOptions, - context); + Core::TextContext({ .session = session })); return result; } @@ -1322,12 +1318,6 @@ void AddUpgradeButton( button->toggleOn(rpl::single(false))->toggledValue( ) | rpl::start_with_next(toggled, button->lifetime()); - const auto makeContext = [session](Fn update) { - return Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = std::move(update), - }; - }; auto star = session->data().customEmojiManager().creditsEmoji(); const auto label = Ui::CreateChild( button, @@ -1339,7 +1329,7 @@ void AddUpgradeButton( Text::WithEntities), st::boxLabel, st::defaultPopupMenu, - std::move(makeContext)); + Core::TextContext({ .session = session })); label->show(); label->setAttribute(Qt::WA_TransparentForMouseEvents); button->widthValue() | rpl::start_with_next([=](int outer) { diff --git a/Telegram/SourceFiles/boxes/translate_box.cpp b/Telegram/SourceFiles/boxes/translate_box.cpp index 39380bd3e..b79a417ee 100644 --- a/Telegram/SourceFiles/boxes/translate_box.cpp +++ b/Telegram/SourceFiles/boxes/translate_box.cpp @@ -150,10 +150,7 @@ void TranslateBox( original->entity()->setAnimationsPausedCallback(animationsPaused); original->entity()->setMarkedText( text, - Core::MarkedTextContext{ - .session = &peer->session(), - .customEmojiRepaint = [=] { original->entity()->update(); }, - }); + Core::TextContext({ .session = &peer->session() })); original->setMinimalHeight(lineHeight); original->hide(anim::type::instant); @@ -221,10 +218,7 @@ void TranslateBox( const auto label = translated->entity(); label->setMarkedText( text, - Core::MarkedTextContext{ - .session = &peer->session(), - .customEmojiRepaint = [=] { label->update(); }, - }); + Core::TextContext({ .session = &peer->session() })); translated->show(anim::type::instant); loading->hide(anim::type::instant); }; diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 927f1359c..1e783b452 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -433,12 +433,9 @@ void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) { const auto session = args.session; field->setTagMimeProcessor( FieldTagMimeProcessor(session, args.allowPremiumEmoji)); - field->setCustomTextContext([=](Fn repaint) { - return std::any(Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = std::move(repaint), - }); - }, [paused] { + field->setCustomTextContext(Core::TextContext({ + .session = session + }), [paused] { return On(PowerSaving::kEmojiChat) || paused(); }, [paused] { return On(PowerSaving::kChatSpoiler) || paused(); diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp index 48325cafb..83d287a16 100644 --- a/Telegram/SourceFiles/core/ui_integration.cpp +++ b/Telegram/SourceFiles/core/ui_integration.cpp @@ -109,6 +109,40 @@ const auto kBadPrefix = u"http://"_q; } // namespace +Ui::Text::MarkedContext TextContext(TextContextArgs &&args) { + using Context = Ui::Text::MarkedContext; + using Factory = Ui::Text::CustomEmojiFactory; + + const auto session = args.session; + auto simple = [session](QStringView data, const Context &context) { + return session->data().customEmojiManager().create( + data, + context.repaint); + }; + auto factory = !args.customEmojiLoopLimit + ? Factory(simple) + : (args.customEmojiLoopLimit > 0) + ? Factory([simple, loop = args.customEmojiLoopLimit]( + QStringView data, + const Context &context) { + return std::make_unique( + simple(data, context), + loop); + }) + : Factory([simple]( + QStringView data, + const Context &context) { + return std::make_unique( + simple(data, context)); + }); + args.details.session = session; + return { + .repaint = std::move(args.repaint), + .customEmojiFactory = std::move(factory), + .other = std::move(args.details), + }; +} + void UiIntegration::postponeCall(FnMut &&callable) { Sandbox::Instance().postponeCall(std::move(callable)); } @@ -149,8 +183,8 @@ bool UiIntegration::screenIsLocked() { std::shared_ptr UiIntegration::createLinkHandler( const EntityLinkData &data, - const std::any &context) { - const auto my = std::any_cast(&context); + const Ui::Text::MarkedContext &context) { + const auto my = std::any_cast(&context.other); switch (data.type) { case EntityType::Url: return (!data.data.isEmpty() @@ -167,7 +201,7 @@ std::shared_ptr UiIntegration::createLinkHandler( return std::make_shared(data.data); case EntityType::Hashtag: - using HashtagMentionType = MarkedTextContext::HashtagMentionType; + using HashtagMentionType = TextContextDetails::HashtagMentionType; if (my && my->type == HashtagMentionType::Twitter) { return std::make_shared( (u"https://twitter.com/hashtag/"_q @@ -187,7 +221,7 @@ std::shared_ptr UiIntegration::createLinkHandler( return std::make_shared(data.data); case EntityType::Mention: - using HashtagMentionType = MarkedTextContext::HashtagMentionType; + using HashtagMentionType = TextContextDetails::HashtagMentionType; if (my && my->type == HashtagMentionType::Twitter) { return std::make_shared( u"https://twitter.com/"_q + data.data.mid(1), @@ -219,7 +253,9 @@ std::shared_ptr UiIntegration::createLinkHandler( case EntityType::Pre: return std::make_shared(data.text, data.type); case EntityType::Phone: - return std::make_shared(my->session, data.text); + return my->session + ? std::make_shared(my->session, data.text) + : nullptr; } return Integration::createLinkHandler(data, context); } @@ -273,39 +309,6 @@ bool UiIntegration::copyPreOnClick(const QVariant &context) { return true; } -std::unique_ptr UiIntegration::createCustomEmoji( - QStringView data, - const std::any &context) { - if (auto simple = Ui::Text::TryMakeSimpleEmoji(data)) { - return simple; - } - const auto my = std::any_cast(&context); - if (!my || !my->session) { - return nullptr; - } - auto result = my->session->data().customEmojiManager().create( - data, - my->customEmojiRepaint); - if (my->customEmojiLoopLimit > 0) { - return std::make_unique( - std::move(result), - my->customEmojiLoopLimit); - } else if (my->customEmojiLoopLimit) { - return std::make_unique( - std::move(result)); - } - return result; -} - -Fn UiIntegration::createSpoilerRepaint(const std::any &context) { - const auto my = std::any_cast(&context); - if (my) { - return my->customEmojiRepaint; - } - const auto common = std::any_cast(&context); - return common ? common->repaint : nullptr; -} - rpl::producer<> UiIntegration::forcePopupMenuHideRequests() { return Core::App().passcodeLockChanges() | rpl::to_empty; } diff --git a/Telegram/SourceFiles/core/ui_integration.h b/Telegram/SourceFiles/core/ui_integration.h index 330d7a84c..fcb9db5ee 100644 --- a/Telegram/SourceFiles/core/ui_integration.h +++ b/Telegram/SourceFiles/core/ui_integration.h @@ -19,7 +19,7 @@ class ElementDelegate; namespace Core { -struct MarkedTextContext { +struct TextContextDetails { enum class HashtagMentionType : uchar { Telegram, Twitter, @@ -28,9 +28,15 @@ struct MarkedTextContext { Main::Session *session = nullptr; HashtagMentionType type = HashtagMentionType::Telegram; - Fn customEmojiRepaint; +}; + +struct TextContextArgs { + not_null session; + TextContextDetails details; + Fn repaint; int customEmojiLoopLimit = 0; }; +[[nodiscard]] Ui::Text::MarkedContext TextContext(TextContextArgs &&args); class UiIntegration final : public Ui::Integration { public: @@ -49,7 +55,7 @@ public: std::shared_ptr createLinkHandler( const EntityLinkData &data, - const std::any &context) override; + const Ui::Text::MarkedContext &context) override; bool handleUrlClick( const QString &url, const QVariant &context) override; @@ -57,10 +63,6 @@ public: rpl::producer<> forcePopupMenuHideRequests() override; const Ui::Emoji::One *defaultEmojiVariant( const Ui::Emoji::One *emoji) override; - std::unique_ptr createCustomEmoji( - QStringView data, - const std::any &context) override; - Fn createSpoilerRepaint(const std::any &context) override; QString phraseContextCopyText() override; QString phraseContextCopyEmail() override; diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index f541fda01..eb34f30df 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -519,8 +519,8 @@ std::unique_ptr CustomEmojiManager::create( Ui::Text::CustomEmojiFactory CustomEmojiManager::factory( SizeTag tag, int sizeOverride) { - return [=](QStringView data, Fn update) { - return create(data, std::move(update), tag, sizeOverride); + return [=](QStringView data, const Ui::Text::MarkedContext &context) { + return create(data, context.repaint, tag, sizeOverride); }; } @@ -1145,8 +1145,9 @@ void InsertCustomEmoji( Ui::Text::CustomEmojiFactory ReactedMenuFactory( not_null session) { return [owner = &session->data()]( - QStringView data, - Fn repaint) -> std::unique_ptr { + QStringView data, + const Ui::Text::MarkedContext &context + ) -> std::unique_ptr { const auto prefix = u"default:"_q; if (data.startsWith(prefix)) { const auto &list = owner->reactions().list( @@ -1166,13 +1167,13 @@ Ui::Text::CustomEmojiFactory ReactedMenuFactory( std::make_unique( owner->customEmojiManager().create( document, - std::move(repaint), + context.repaint, tag, size), QPoint(skip, skip))); } } - return owner->customEmojiManager().create(data, std::move(repaint)); + return owner->customEmojiManager().create(data, context.repaint); }; } diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 54ea9eb48..42e67637b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -4308,10 +4308,7 @@ QImage *InnerWidget::cacheChatsFilterTag( const auto color = Ui::EmptyUserpic::UserpicColor(colorIndex).color2; entry.context.color = color->c; entry.context.active = active; - entry.context.textContext = Core::MarkedTextContext{ - .session = &session(), - .customEmojiRepaint = [] {}, - }; + entry.context.textContext = Core::TextContext({ .session = &session() }); entry.frame = Ui::ChatsFilterTag(roundedText, entry.context); return &entry.frame; } diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp b/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp index c7e85c94e..6b105f094 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp @@ -60,11 +60,10 @@ namespace { st::dialogsSearchTagArrow, st::dialogsSearchTagArrowPadding)); auto result = Ui::Text::String(); - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &owner->session(), - .customEmojiRepaint = [] {}, .customEmojiLoopLimit = 1, - }; + }); const auto attempt = [&](const auto &phrase) { result.setMarkedText( st::dialogsSearchTagPromo, diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 729224305..39a8e3826 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -580,10 +580,10 @@ void PaintRow( {}, true))).append(std::move(draftText)); } - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &thread->session(), - .customEmojiRepaint = customEmojiRepaint, - }; + .repaint = customEmojiRepaint, + }); cache.setMarkedText( st::dialogsTextStyle, std::move(draftText), diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp index caed2612e..51fb657cd 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp @@ -174,11 +174,11 @@ void MessageView::prepare( : nullptr; const auto hasImages = !preview.images.empty(); const auto history = item->history(); - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &history->session(), - .customEmojiRepaint = customEmojiRepaint, + .repaint = customEmojiRepaint, .customEmojiLoopLimit = kEmojiLoopCount, - }; + }); const auto senderTill = (preview.arrowInTextPosition > 0) ? preview.arrowInTextPosition : preview.imagesInTextPosition; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp index cd312fd85..82fc64a7a 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp @@ -63,11 +63,11 @@ void TopicsView::prepare(MsgId frontRootId, Fn customEmojiRepaint) { && title.version == topic->titleVersion()) { continue; } - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &topic->session(), - .customEmojiRepaint = customEmojiRepaint, + .repaint = customEmojiRepaint, .customEmojiLoopLimit = kIconLoopCount, - }; + }); auto topicTitle = topic->titleWithIcon(); title.topicRootId = rootId; title.version = topic->titleVersion(); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index a62fa7b48..129eed4de 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1169,12 +1169,6 @@ void History::applyServiceChanges( } if (paid) { // Toast on a current active window. - const auto context = [=](not_null toast) { - return Core::MarkedTextContext{ - .session = &session(), - .customEmojiRepaint = [=] { toast->update(); }, - }; - }; Ui::Toast::Show({ .text = tr::lng_payments_success( tr::now, @@ -1185,7 +1179,9 @@ void History::applyServiceChanges( lt_title, Ui::Text::Bold(paid->title), Ui::Text::WithEntities), - .textContext = context, + .textContext = Core::TextContext({ + .session = &session(), + }), }); } } diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index d71e861f5..9b3f23fb1 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -205,7 +205,9 @@ void HistoryMessageForwarded::create( const HistoryMessageVia *via, not_null item) const { auto phrase = TextWithEntities(); - auto context = Core::MarkedTextContext{}; + auto context = Core::TextContext({ + .session = &item->history()->session(), + }); const auto fromChannel = originalSender && originalSender->isChannel() && !originalSender->isMegagroup(); @@ -215,8 +217,7 @@ void HistoryMessageForwarded::create( : originalHiddenSenderInfo->name) }; if (const auto copy = originalSender) { - context.session = ©->owner().session(); - context.customEmojiRepaint = [=] { + context.repaint = [=] { // It is important to capture here originalSender by value, // not capture the HistoryMessageForwarded* and read the // originalSender field, because the components themselves @@ -225,7 +226,7 @@ void HistoryMessageForwarded::create( copy->owner().requestItemRepaint(item); }; phrase = Ui::Text::SingleCustomEmoji( - context.session->data().customEmojiManager().peerUserpicEmojiData( + copy->owner().customEmojiManager().peerUserpicEmojiData( copy, st::fwdTextUserpicPadding)); } @@ -755,10 +756,10 @@ ReplyKeyboard::ReplyKeyboard( _st->textStyle(), TextUtilities::SingleLine(textWithEntities), kMarkupTextOptions, - Core::MarkedTextContext{ + Core::TextContext({ .session = &item->history()->owner().session(), - .customEmojiRepaint = [=] { _st->repaint(item); }, - }); + .repaint = [=] { _st->repaint(item); }, + })); } else { button.text.setText( _st->textStyle(), diff --git a/Telegram/SourceFiles/history/history_view_top_toast.cpp b/Telegram/SourceFiles/history/history_view_top_toast.cpp index 7736f971d..a68061b9f 100644 --- a/Telegram/SourceFiles/history/history_view_top_toast.cpp +++ b/Telegram/SourceFiles/history/history_view_top_toast.cpp @@ -31,16 +31,10 @@ void InfoTooltip::show( not_null session, const TextWithEntities &text, Fn hiddenCallback) { - const auto context = [=](not_null toast) { - return Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = [=] { toast->update(); }, - }; - }; hide(anim::type::normal); _topToast = Ui::Toast::Show(parent, Ui::Toast::Config{ .text = text, - .textContext = context, + .textContext = Core::TextContext({ .session = session }), .st = &st::historyInfoToast, .attach = RectPart::Top, .duration = CountToastDuration(text), diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 76353c79f..ca5be0a39 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -8829,10 +8829,10 @@ void HistoryWidget::messageDataReceived( } void HistoryWidget::updateReplyEditText(not_null item) { - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &session(), - .customEmojiRepaint = [=] { updateField(); }, - }; + .repaint = [=] { updateField(); }, + }); _replyEditMsgText.setMarkedText( st::defaultTextStyle, ((_editMsgId || _replyTo.quote.empty()) @@ -8918,11 +8918,10 @@ void HistoryWidget::updateReplyToName() { } else if (!_replyEditMsg && (_replyTo || !_kbReplyTo)) { return; } - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &_history->session(), - .customEmojiRepaint = [] {}, .customEmojiLoopLimit = 1, - }; + }); const auto to = _replyEditMsg ? _replyEditMsg : _kbReplyTo; const auto replyToQuote = _replyTo && !_replyTo.quote.empty(); _replyToName.setMarkedText( diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 0d277b016..a0b676e44 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -428,10 +428,10 @@ void FieldHeader::init() { void FieldHeader::updateShownMessageText() { Expects(_shownMessage != nullptr); - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &_data->session(), - .customEmojiRepaint = [=] { customEmojiRepaint(); }, - }; + .repaint = [=] { customEmojiRepaint(); }, + }); const auto reply = replyingToMessage(); _shownMessageText.setMarkedText( st::messageTextStyle, @@ -464,11 +464,10 @@ void FieldHeader::setShownMessage(HistoryItem *item) { tr::lng_edit_message(tr::now), Ui::NameTextOptions()); } else if (item) { - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &_history->session(), - .customEmojiRepaint = [] {}, .customEmojiLoopLimit = 1, - }; + }); const auto replyTo = _replyTo.current(); const auto quote = replyTo && !replyTo.quote.empty(); _shownMessageName.setMarkedText( diff --git a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp index 022b124ab..7502a93c8 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -190,10 +190,10 @@ void ForwardPanel::updateTexts() { } } _from.setText(st::msgNameStyle, from, Ui::NameTextOptions()); - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &_to->session(), - .customEmojiRepaint = _repaint, - }; + .repaint = _repaint, + }); _text.setMarkedText( st::defaultTextStyle, text, diff --git a/Telegram/SourceFiles/history/view/history_view_about_view.cpp b/Telegram/SourceFiles/history/view/history_view_about_view.cpp index f97e94355..ca6eb9a83 100644 --- a/Telegram/SourceFiles/history/view/history_view_about_view.cpp +++ b/Telegram/SourceFiles/history/view/history_view_about_view.cpp @@ -234,10 +234,10 @@ auto GenerateNewPeerInfo( fadedFg, normalFg)); - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &parent->history()->session(), - .customEmojiRepaint = [parent] { parent->repaint(); }, - }; + .repaint = [parent] { parent->repaint(); }, + }); const auto details = user->botVerifyDetails(); const auto text = details ? Data::SingleCustomEmoji( diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp index 60e1b4e86..71e711ad0 100644 --- a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp +++ b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp @@ -163,7 +163,7 @@ public: void showState( State state, TextWithEntities status, - Fn customEmojiRepaint)> context); + Ui::Text::MarkedContext context); [[nodiscard]] rpl::producer<> unarchiveClicks() const; [[nodiscard]] rpl::producer<> addClicks() const; @@ -272,7 +272,7 @@ ContactStatus::Bar::Bar( void ContactStatus::Bar::showState( State state, TextWithEntities status, - Fn customEmojiRepaint)> context) { + Ui::Text::MarkedContext context) { using Type = State::Type; const auto type = state.type; _add->setVisible(type == Type::AddOrBlock || type == Type::Add); @@ -294,6 +294,7 @@ void ContactStatus::Bar::showState( _emojiStatusShadow->setVisible( has && (type == Type::AddOrBlock || type == Type::UnarchiveOrBlock)); if (has) { + context.repaint = [=] { emojiStatusRepaint(); }; _emojiStatusInfo->entity()->setMarkedText( tr::lng_new_contact_about_status( tr::now, @@ -303,7 +304,7 @@ void ContactStatus::Bar::showState( Ui::Text::Link( tr::lng_new_contact_about_status_link(tr::now)), Ui::Text::WithEntities), - context([=] { emojiStatusRepaint(); })); + context); _emojiStatusInfo->entity()->overrideLinkClickHandler([=] { _emojiStatusClicks.fire({}); }); @@ -628,12 +629,7 @@ void ContactStatus::setupState(not_null peer, bool showInForum) { peer->session().api().requestPeerSettings(peer); } - _context = [=](Fn customEmojiRepaint) { - return Core::MarkedTextContext{ - .session = &peer->session(), - .customEmojiRepaint = customEmojiRepaint, - }; - }; + _context = Core::TextContext({ .session = &peer->session() }); _inner->showState({}, {}, _context); const auto channel = peer->asChannel(); rpl::combine( diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.h b/Telegram/SourceFiles/history/view/history_view_contact_status.h index 56be475a8..0c540a36c 100644 --- a/Telegram/SourceFiles/history/view/history_view_contact_status.h +++ b/Telegram/SourceFiles/history/view/history_view_contact_status.h @@ -117,7 +117,7 @@ private: const not_null _controller; State _state; TextWithEntities _status; - Fn customEmojiRepaint)> _context; + Ui::Text::MarkedContext _context; QPointer _inner; SlidingBar _bar; bool _hiddenByForum = false; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 44e9e658f..cd8139c9e 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -1171,10 +1171,10 @@ void Element::validateText() { void Element::setTextWithLinks( const TextWithEntities &text, const std::vector &links) { - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &history()->session(), - .customEmojiRepaint = [=] { customEmojiRepaint(); }, - }; + .repaint = [=] { customEmojiRepaint(); }, + }); if (_flags & Flag::ServiceMessage) { const auto &options = Ui::ItemTextServiceOptions(); _text.setMarkedText(st::serviceTextStyle, text, options, context); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index b9a4b0a1e..5e8d1c05b 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -550,11 +550,10 @@ void Message::refreshRightBadge() { if (badge.empty()) { _rightBadge.clear(); } else { - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &item->history()->session(), - .customEmojiRepaint = [] {}, .customEmojiLoopLimit = 1, - }; + }); _rightBadge.setMarkedText( st::defaultTextStyle, badge, @@ -1049,11 +1048,11 @@ void Message::refreshTopicButton() { _topicButton->link = MakeTopicButtonLink(topic, jumpToId); if (_topicButton->nameVersion != topic->titleVersion()) { _topicButton->nameVersion = topic->titleVersion(); - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &history()->session(), - .customEmojiRepaint = [=] { customEmojiRepaint(); }, + .repaint = [=] { customEmojiRepaint(); }, .customEmojiLoopLimit = 1, - }; + }); _topicButton->name.setMarkedText( st::fwdTextStyle, topic->titleWithIcon(), diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp index eebbb1848..9564b94c5 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp @@ -29,10 +29,10 @@ namespace { Fn repaint) { return Ui::MessageBarContent{ .text = item->inReplyText(), - .context = Core::MarkedTextContext{ + .context = Core::TextContext({ .session = &item->history()->session(), - .customEmojiRepaint = std::move(repaint), - }, + .repaint = std::move(repaint), + }), }; } diff --git a/Telegram/SourceFiles/history/view/history_view_reply.cpp b/Telegram/SourceFiles/history/view/history_view_reply.cpp index d2043b027..3d29c806f 100644 --- a/Telegram/SourceFiles/history/view/history_view_reply.cpp +++ b/Telegram/SourceFiles/history/view/history_view_reply.cpp @@ -244,10 +244,10 @@ void Reply::update( }).text : TextWithEntities(); const auto repaint = [=] { item->customEmojiRepaint(); }; - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &view->history()->session(), - .customEmojiRepaint = repaint, - }; + .repaint = repaint, + }); _text.setMarkedText( st::defaultTextStyle, text, @@ -463,11 +463,10 @@ void Reply::updateName( if (!viaBotUsername.isEmpty()) { nameFull.append(u" @"_q).append(viaBotUsername); } - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &history->session(), - .customEmojiRepaint = [] {}, .customEmojiLoopLimit = 1, - }; + }); _name.setMarkedText( st::fwdTextStyle, nameFull, diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.cpp b/Telegram/SourceFiles/history/view/media/history_view_game.cpp index 5bd71f4e4..cd3950bbe 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_game.cpp @@ -38,10 +38,10 @@ Game::Game( , _title(st::msgMinWidth - _st.padding.left() - _st.padding.right()) , _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) { if (!consumed.text.isEmpty()) { - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &history()->session(), - .customEmojiRepaint = [=] { _parent->customEmojiRepaint(); }, - }; + .repaint = [=] { _parent->customEmojiRepaint(); }, + }); _description.setMarkedText( st::webPageDescriptionStyle, consumed, @@ -503,10 +503,10 @@ void Game::parentTextUpdated() { if (const auto media = _parent->data()->media()) { const auto consumed = media->consumedMessageText(); if (!consumed.text.isEmpty()) { - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &history()->session(), - .customEmojiRepaint = [=] { _parent->customEmojiRepaint(); }, - }; + .repaint = [=] { _parent->customEmojiRepaint(); }, + }); _description.setMarkedText( st::webPageDescriptionStyle, consumed, diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index d41c93feb..1d3554972 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -244,10 +244,11 @@ void Media::drawPurchasedTag( const auto session = &item->history()->session(); auto text = Ui::Text::Colorized(Ui::CreditsEmojiSmall(session)); text.append(Lang::FormatCountDecimal(amount)); - purchased->text.setMarkedText(st::defaultTextStyle, text, kMarkupTextOptions, Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = [] {}, - }); + purchased->text.setMarkedText( + st::defaultTextStyle, + text, + kMarkupTextOptions, + Core::TextContext({ .session = session })); } const auto st = context.st; @@ -413,10 +414,7 @@ void Media::drawSpoilerTag( price, Ui::Text::WithEntities), kMarkupTextOptions, - Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = [] {}, - }); + Core::TextContext({ .session = session })); } const auto width = iconSkip + text.maxWidth(); const auto inner = QRect(0, 0, width, text.minHeight()); @@ -541,10 +539,10 @@ Ui::Text::String Media::createCaption(not_null item) const { - st::msgPadding.left() - st::msgPadding.right(); auto result = Ui::Text::String(minResizeWidth); - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &history()->session(), - .customEmojiRepaint = [=] { _parent->customEmojiRepaint(); }, - }; + .repaint = [=] { _parent->customEmojiRepaint(); }, + }); result.setMarkedText( st::messageTextStyle, item->translatedTextWithLocalEntities(), diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp index f2c970e40..3b70d6282 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp @@ -236,7 +236,7 @@ MediaGenericTextPart::MediaGenericTextPart( QMargins margins, const style::TextStyle &st, const base::flat_map &links, - const std::any &context) + const Ui::Text::MarkedContext &context) : _text(st::msgMinWidth) , _margins(margins) { _text.setMarkedText( diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_generic.h b/Telegram/SourceFiles/history/view/media/history_view_media_generic.h index 437c665a4..0fd011914 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_generic.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_generic.h @@ -141,7 +141,7 @@ public: QMargins margins, const style::TextStyle &st = st::defaultTextStyle, const base::flat_map &links = {}, - const std::any &context = {}); + const Ui::Text::MarkedContext &context = {}); void draw( Painter &p, diff --git a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp index 72b1df9ba..bc9386a1d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/media/history_view_poll.h" -#include "core/ui_integration.h" // Core::MarkedTextContext. +#include "core/ui_integration.h" // TextContext #include "lang/lang_keys.h" #include "history/history.h" #include "history/history_item.h" @@ -158,7 +158,7 @@ struct Poll::Answer { void fillData( not_null poll, const PollAnswer &original, - Core::MarkedTextContext context); + Ui::Text::MarkedContext context); Ui::Text::String text; QByteArray option; @@ -206,7 +206,7 @@ Poll::Answer::Answer() : text(st::msgMinWidth / 2) { void Poll::Answer::fillData( not_null poll, const PollAnswer &original, - Core::MarkedTextContext context) { + Ui::Text::MarkedContext context) { chosen = original.chosen; correct = poll->quiz() ? original.correct : chosen; if (!text.isEmpty() && text.toTextWithEntities() == original.text) { @@ -396,11 +396,11 @@ void Poll::updateTexts() { st::historyPollQuestionStyle, _poll->question, options, - Core::MarkedTextContext{ + Core::TextContext({ .session = &_poll->session(), - .customEmojiRepaint = [=] { repaint(); }, + .repaint = [=] { repaint(); }, .customEmojiLoopLimit = 2, - }); + })); } if (_flags != _poll->flags() || _subtitle.isEmpty()) { using Flag = PollData::Flag; @@ -525,11 +525,11 @@ void Poll::updateRecentVoters() { } void Poll::updateAnswers() { - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &_poll->session(), - .customEmojiRepaint = [=] { repaint(); }, + .repaint = [=] { repaint(); }, .customEmojiLoopLimit = 2, - }; + }); const auto changed = !ranges::equal( _answers, _poll->answers, diff --git a/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp b/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp index 08444c490..3cf6d7b77 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp @@ -47,10 +47,10 @@ ServiceBox::ServiceBox( _content->title(), kMarkupTextOptions, _maxWidth, - Core::MarkedTextContext{ + Core::TextContext({ .session = &parent->history()->session(), - .customEmojiRepaint = [parent] { parent->customEmojiRepaint(); }, - }) + .repaint = [parent] { parent->customEmojiRepaint(); }, + })) , _subtitle( st::premiumPreviewAbout.style, Ui::Text::Filtered( @@ -65,10 +65,10 @@ ServiceBox::ServiceBox( }), kMarkupTextOptions, _maxWidth, - Core::MarkedTextContext{ + Core::TextContext({ .session = &parent->history()->session(), - .customEmojiRepaint = [parent] { parent->customEmojiRepaint(); }, - }) + .repaint = [parent] { parent->customEmojiRepaint(); }, + })) , _size( _content->width(), (st::msgServiceGiftBoxTopSkip diff --git a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp index 015ef507a..287fbc9f1 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp @@ -441,7 +441,7 @@ TextPartColored::TextPartColored( Fn color, const style::TextStyle &st, const base::flat_map &links, - const std::any &context) + const Ui::Text::MarkedContext &context) : MediaGenericTextPart(text, margins, st, links, context) , _color(std::move(color)) { } @@ -458,7 +458,7 @@ AttributeTable::AttributeTable( QMargins margins, Fn labelColor, Fn valueColor, - const std::any &context) + const Ui::Text::MarkedContext &context) : _margins(margins) , _labelColor(std::move(labelColor)) , _valueColor(std::move(valueColor)) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h index 269065076..3110a618e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h +++ b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h @@ -66,7 +66,7 @@ public: Fn color, const style::TextStyle &st = st::defaultTextStyle, const base::flat_map &links = {}, - const std::any &context = {}); + const Ui::Text::MarkedContext &context = {}); private: void setupPen( @@ -90,7 +90,7 @@ public: QMargins margins, Fn labelColor, Fn valueColor, - const std::any &context = {}); + const Ui::Text::MarkedContext &context = {}); void draw( Painter &p, diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index b0ab08952..160962ee5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -379,11 +379,10 @@ QSize WebPage::countOptimalSize() { // Detect _openButtonWidth before counting paddings. _openButton = Ui::Text::String(); if (HasButton(_data)) { - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &_data->session(), - .customEmojiRepaint = [] {}, .customEmojiLoopLimit = 1, - }; + }); _openButton.setMarkedText( st::semiboldTextStyle, PageToPhrase(_data), @@ -539,16 +538,18 @@ QSize WebPage::countOptimalSize() { _description = Ui::Text::String(st::minPhotoSize - rect::m::sum::h(padding)); } - using MarkedTextContext = Core::MarkedTextContext; - auto context = MarkedTextContext{ + using Type = Core::TextContextDetails::HashtagMentionType; + auto context = Core::TextContext({ .session = &history()->session(), - .customEmojiRepaint = [=] { _parent->customEmojiRepaint(); }, - }; - if (_data->siteName == u"Twitter"_q) { - context.type = MarkedTextContext::HashtagMentionType::Twitter; - } else if (_data->siteName == u"Instagram"_q) { - context.type = MarkedTextContext::HashtagMentionType::Instagram; - } + .details = { + .type = ((_data->siteName == u"Twitter"_q) + ? Type::Twitter + : (_data->siteName == u"Instagram"_q) + ? Type::Instagram + : Type::Telegram), + }, + .repaint = [=] { _parent->customEmojiRepaint(); }, + }); _description.setMarkedText( st::webPageDescriptionStyle, text, diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.cpp index 209e73e3c..90c6d52fa 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.cpp @@ -154,7 +154,7 @@ Row::Row( : PeerListRow(peer, id) , _custom(reactionEntityData.isEmpty() ? nullptr - : factory(reactionEntityData, [=] { repaint(this); })) + : factory(reactionEntityData, { .repaint = [=] { repaint(this); } })) , _paused(std::move(paused)) { } diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_tabs.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_tabs.cpp index f3b23804f..df06ba49f 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_tabs.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_tabs.cpp @@ -58,7 +58,7 @@ not_null CreateTab( ? nullptr : factory( Data::ReactionEntityData(reaction), - [=] { result->update(); }); + { .repaint = [=] { result->update(); } }); result->paintRequest( ) | rpl::start_with_next([=] { diff --git a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp index acc7fa153..a088bc958 100644 --- a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp +++ b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp @@ -600,12 +600,6 @@ object_ptr JoinStarRefBox( if (const auto average = program.revenuePerUser) { const auto layout = box->verticalLayout(); const auto session = &initialRecipient->session(); - const auto makeContext = [session](Fn update) { - return Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = std::move(update), - }; - }; auto text = Ui::Text::Colorized(Ui::CreditsEmoji(session)); text.append(Lang::FormatStarsAmountRounded(average)); layout->add( @@ -618,7 +612,7 @@ object_ptr JoinStarRefBox( Ui::Text::WithEntities), st::starrefRevenueText, st::defaultPopupMenu, - makeContext), + Core::TextContext({ .session = session })), st::boxRowPadding); Ui::AddSkip(layout, st::defaultVerticalListSkip); } diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp index b39200c7e..4b319c310 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_peer_color_box.h" // AddLevelBadge. #include "chat_helpers/stickers_emoji_pack.h" #include "core/application.h" +#include "core/ui_integration.h" // TextContext. #include "data/components/credits.h" #include "data/data_channel.h" #include "data/data_premium_limits.h" @@ -402,12 +403,6 @@ void InnerWidget::fill() { const auto session = &_peer->session(); const auto withdrawalEnabled = WithdrawalEnabled(session); - const auto makeContext = [=](not_null l) { - return Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = [=] { l->update(); }, - }; - }; const auto addEmojiToMajor = [=]( not_null label, rpl::producer value, @@ -433,7 +428,7 @@ void InnerWidget::fill() { ) | rpl::start_with_next([=](EarnInt v) { label->setMarkedText( base::duplicate(prepended).append(icon).append(MajorPart(v)), - makeContext(label)); + Core::TextContext({ .session = session })); }, label->lifetime()); }; @@ -445,11 +440,7 @@ void InnerWidget::fill() { st::channelEarnCurrencyLearnMargins, false)); - const auto arrow = Ui::Text::SingleCustomEmoji( - session->data().customEmojiManager().registerInternalEmoji( - st::topicButtonArrow, - st::channelEarnLearnArrowMargins, - true)); + const auto arrow = Ui::Text::IconEmoji(&st::textMoreIconEmoji); const auto addAboutWithLearn = [&](const tr::phrase &text) { auto label = Ui::CreateLabelWithCustomEmoji( container, @@ -463,7 +454,7 @@ void InnerWidget::fill() { return Ui::Text::Link(std::move(text), 1); }), Ui::Text::RichLangValue), - { .session = session }, + Core::TextContext({ .session = session }), st::boxDividerLabel); label->setLink(1, std::make_shared([=] { _show->showBox(Box([=](not_null box) { @@ -580,7 +571,7 @@ void InnerWidget::fill() { Ui::Text::Link(bigCurrencyIcon, 1)), Ui::Text::RichLangValue ), - { .session = session }, + Core::TextContext({ .session = session }), st::boxTitle)))->entity(); const auto diamonds = l->lifetime().make_state(0); l->setLink(1, std::make_shared([=] { @@ -610,7 +601,7 @@ void InnerWidget::fill() { }), Ui::Text::RichLangValue ), - { .session = session }, + Core::TextContext({ .session = session }), st::channelEarnLearnDescription)); label->resizeToWidth(box->width() - rect::m::sum::h(st::boxRowPadding)); @@ -1522,4 +1513,3 @@ not_null InnerWidget::peer() const { } } // namespace Info::ChannelEarn - diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp index 45d424fc0..803e7c433 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp @@ -573,11 +573,8 @@ TextWithEntities Delegate::ministar() { return owner->customEmojiManager().ministarEmoji({ 0, top, 0, 0 }); } -std::any Delegate::textContext() { - return Core::MarkedTextContext{ - .session = &_window->session(), - .customEmojiRepaint = [] {}, - }; +Ui::Text::MarkedContext Delegate::textContext() { + return Core::TextContext({ .session = &_window->session() }); } QSize Delegate::buttonSize() { diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h index ddd800b3d..0d93a8bf2 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h @@ -105,7 +105,7 @@ class GiftButtonDelegate { public: [[nodiscard]] virtual TextWithEntities star() = 0; [[nodiscard]] virtual TextWithEntities ministar() = 0; - [[nodiscard]] virtual std::any textContext() = 0; + [[nodiscard]] virtual Ui::Text::MarkedContext textContext() = 0; [[nodiscard]] virtual QSize buttonSize() = 0; [[nodiscard]] virtual QMargins buttonExtend() = 0; [[nodiscard]] virtual auto buttonPatternEmoji( @@ -181,7 +181,7 @@ public: TextWithEntities star() override; TextWithEntities ministar() override; - std::any textContext() override; + Ui::Text::MarkedContext textContext() override; QSize buttonSize() override; QMargins buttonExtend() override; auto buttonPatternEmoji( diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index b313aee0e..5f1f66b78 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -895,10 +895,10 @@ rpl::producer AddCurrencyAction( .append(QChar(' ')) .append(Info::ChannelEarn::MajorPart(balance)) .append(Info::ChannelEarn::MinorPart(balance)), - Core::MarkedTextContext{ + Core::TextContext({ .session = &user->session(), - .customEmojiRepaint = [=] { name->update(); }, - }); + .repaint = [=] { name->update(); }, + })); name->resizeToNaturalWidth(available); name->moveToRight(st::settingsButtonRightSkip, st.padding.top()); }, name->lifetime()); @@ -968,10 +968,10 @@ rpl::producer AddCreditsAction( base::duplicate(icon) .append(QChar(' ')) .append(Lang::FormatStarsAmountDecimal(balance)), - Core::MarkedTextContext{ + Core::TextContext({ .session = &user->session(), - .customEmojiRepaint = [=] { name->update(); }, - }); + .repaint = [=] { name->update(); }, + })); name->resizeToNaturalWidth(available); name->moveToRight(st::settingsButtonRightSkip, st.padding.top()); }, name->lifetime()); diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index e08bc563e..6a5e27cf0 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peer_list_controllers.h" #include "boxes/peer_list_widgets.h" #include "chat_helpers/stickers_gift_box_pack.h" -#include "core/ui_integration.h" // Core::MarkedTextContext. +#include "core/ui_integration.h" // TextContext #include "data/data_channel.h" #include "data/data_credits.h" #include "data/data_session.h" @@ -747,9 +747,10 @@ rpl::producer BoostsController::totalBoostsValue() const { class CreditsRow final : public PeerListRow { public: struct Descriptor final { + not_null session; Data::CreditsHistoryEntry entry; Data::SubscriptionEntry subscription; - Core::MarkedTextContext context; + Ui::Text::MarkedContext context; int rowHeight = 0; Fn)> updateCallback; }; @@ -790,9 +791,10 @@ public: private: void init(); + const not_null _session; const Data::CreditsHistoryEntry _entry; const Data::SubscriptionEntry _subscription; - const Core::MarkedTextContext _context; + const Ui::Text::MarkedContext _context; const int _rowHeight; PaintRoundImageCallback _paintUserpicCallback; @@ -812,6 +814,7 @@ CreditsRow::CreditsRow( not_null peer, const Descriptor &descriptor) : PeerListRow(peer, UniqueRowIdFromEntry(descriptor.entry)) +, _session(descriptor.session) , _entry(descriptor.entry) , _subscription(descriptor.subscription) , _context(descriptor.context) @@ -837,6 +840,7 @@ CreditsRow::CreditsRow( CreditsRow::CreditsRow(const Descriptor &descriptor) : PeerListRow(UniqueRowIdFromEntry(descriptor.entry)) +, _session(descriptor.session) , _entry(descriptor.entry) , _subscription(descriptor.subscription) , _context(descriptor.context) @@ -902,19 +906,19 @@ void CreditsRow::init() { : _subscription.photoId; if (descriptionPhotoId) { _descriptionThumbnail = Ui::MakePhotoThumbnail( - _context.session->data().photo(descriptionPhotoId), + _session->data().photo(descriptionPhotoId), {}); _descriptionThumbnail->subscribeToUpdates([this] { const auto thumbnailSide = st::defaultTextStyle.font->height; _descriptionThumbnailCache = Images::Round( _descriptionThumbnail->image(thumbnailSide), ImageRoundRadius::Large); - if (_context.customEmojiRepaint) { - _context.customEmojiRepaint(); + if (_context.repaint) { + _context.repaint(); } }); } - auto &manager = _context.session->data().customEmojiManager(); + auto &manager = _session->data().customEmojiManager(); if (_entry) { constexpr auto kMinus = QChar(0x2212); _rightText.setMarkedText( @@ -930,9 +934,9 @@ void CreditsRow::init() { if (!_paintUserpicCallback) { _paintUserpicCallback = _entry.stargift ? Ui::GenerateGiftStickerUserpicCallback( - _context.session, + _session, _entry.bareGiftStickerId, - _context.customEmojiRepaint) + _context.repaint) : !isSpecial ? PeerListRow::generatePaintUserpicCallback(false) : Ui::GenerateCreditsPaintUserpicCallback(_entry); @@ -1110,7 +1114,7 @@ private: Api::CreditsHistory _api; Data::CreditsStatusSlice _firstSlice; Data::CreditsStatusSlice::OffsetToken _apiToken; - Core::MarkedTextContext _context; + Ui::Text::MarkedContext _context; rpl::variable _allLoaded = false; bool _requesting = false; @@ -1123,10 +1127,7 @@ CreditsController::CreditsController(CreditsDescriptor d) , _entryClickedCallback(std::move(d.entryClickedCallback)) , _api(d.peer, d.in, d.out) , _firstSlice(std::move(d.firstSlice)) -, _context(Core::MarkedTextContext{ - .session = _session, - .customEmojiRepaint = [] {}, -}) { +, _context(Core::TextContext({ .session = _session })) { PeerListController::setStyleOverrides(&st::creditsHistoryEntriesList); } @@ -1166,6 +1167,7 @@ void CreditsController::applySlice(const Data::CreditsStatusSlice &slice) { const Data::CreditsHistoryEntry &i, const Data::SubscriptionEntry &s) { const auto descriptor = CreditsRow::Descriptor{ + .session = &session(), .entry = i, .subscription = s, .context = _context, diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_recent_message.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_recent_message.cpp index 2f8b1d069..1a5da3a0d 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_recent_message.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_recent_message.cpp @@ -87,10 +87,10 @@ MessagePreview::MessagePreview( st::defaultPeerListItem.nameStyle, item->toPreview({ .generateImages = false }).text, Ui::DialogTextOptions(), - Core::MarkedTextContext{ + Core::TextContext({ .session = &item->history()->session(), - .customEmojiRepaint = [=] { update(); }, - }); + .repaint = [=] { update(); }, + })); if (item->media() && item->media()->hasSpoiler()) { _spoiler = std::make_unique([=] { update(); }); } @@ -131,10 +131,10 @@ MessagePreview::MessagePreview( st::defaultPeerListItem.nameStyle, { tr::lng_in_dlg_story(tr::now) }, Ui::DialogTextOptions(), - Core::MarkedTextContext{ + Core::TextContext({ .session = &story->peer()->session(), - .customEmojiRepaint = [=] { update(); }, - }); + .repaint = [=] { update(); }, + })); if (_preview.isNull()) { if (const auto photo = story->photo()) { _photoMedia = photo->createMediaView(); diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 6d112e5d3..d7fad9222 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/click_handler_types.h" #include "core/local_url_handlers.h" #include "core/shortcuts.h" +#include "core/ui_integration.h" // TextContext #include "data/components/location_pickers.h" #include "data/data_bot_app.h" #include "data/data_changes.h" @@ -380,21 +381,16 @@ void FillBotUsepic( not_null box, not_null bot, base::weak_ptr weak) { - auto arrow = Ui::Text::SingleCustomEmoji( - bot->owner().customEmojiManager().registerInternalEmoji( - st::topicButtonArrow, - st::channelEarnLearnArrowMargins, - true)); auto aboutLabel = Ui::CreateLabelWithCustomEmoji( box->verticalLayout(), tr::lng_allow_bot_webview_details( lt_emoji, - rpl::single(std::move(arrow)), + rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)), Ui::Text::RichLangValue ) | rpl::map([](TextWithEntities text) { return Ui::Text::Link(std::move(text), u"internal:"_q); }), - { .session = &bot->session() }, + Core::TextContext({ .session = &bot->session() }), st::defaultFlatLabel); const auto userpic = Ui::CreateChild( box->verticalLayout(), @@ -446,12 +442,6 @@ std::unique_ptr MakeEmojiSetStatusPreview( not_null parent, not_null peer, not_null document) { - const auto makeContext = [=](Fn update) { - return Core::MarkedTextContext{ - .session = &peer->session(), - .customEmojiRepaint = update, - }; - }; const auto emoji = Ui::CreateChild>( parent, object_ptr( @@ -464,7 +454,7 @@ std::unique_ptr MakeEmojiSetStatusPreview( : QString()))), st::botEmojiStatusEmoji, st::defaultPopupMenu, - makeContext), + Core::TextContext({ .session = &peer->session() })), style::margins(st::normalFont->spacew, 0, 0, 0)); emoji->entity()->resizeToWidth(emoji->entity()->textMaxWidth()); diff --git a/Telegram/SourceFiles/intro/intro_step.cpp b/Telegram/SourceFiles/intro/intro_step.cpp index c8d9a3d59..969aecbb0 100644 --- a/Telegram/SourceFiles/intro/intro_step.cpp +++ b/Telegram/SourceFiles/intro/intro_step.cpp @@ -104,13 +104,7 @@ Step::Step( text.entities, EntityType::Spoiler, &EntityInText::type); - if (hasSpoiler) { - label->setMarkedText( - text, - CommonTextContext{ [=] { label->update(); } }); - } else { - label->setMarkedText(text); - } + label->setMarkedText(text); label->setAttribute(Qt::WA_TransparentForMouseEvents, hasSpoiler); updateLabelsPosition(); }, lifetime()); diff --git a/Telegram/SourceFiles/media/stories/media_stories_caption_full_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_caption_full_view.cpp index f3c4a112f..218add340 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_caption_full_view.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_caption_full_view.cpp @@ -30,10 +30,9 @@ CaptionFullView::CaptionFullView(not_null controller) object_ptr(_scroll.get(), st::storiesCaptionFull), st::mediaviewCaptionPadding + _controller->repostCaptionPadding()))) , _text(_wrap->entity()) { - _text->setMarkedText(controller->captionText(), Core::MarkedTextContext{ + _text->setMarkedText(controller->captionText(), Core::TextContext({ .session = &controller->uiShow()->session(), - .customEmojiRepaint = [=] { _text->update(); }, - }); + })); startAnimation(); _controller->layoutValue( diff --git a/Telegram/SourceFiles/media/stories/media_stories_header.cpp b/Telegram/SourceFiles/media/stories/media_stories_header.cpp index 7277109c3..a77c700b2 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_header.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_header.cpp @@ -403,10 +403,7 @@ void Header::show(HeaderData data) { const auto prefix = data.fromPeer ? data.fromPeer : data.repostPeer; _repost->setMarkedText( (prefix ? Ui::Text::Link(prefixName) : prefixName), - Core::MarkedTextContext{ - .session = &data.peer->session(), - .customEmojiRepaint = [=] { _repost->update(); }, - }); + Core::TextContext({ .session = &data.peer->session() })); if (prefix) { _repost->setClickHandlerFilter([=](const auto &...) { _controller->uiShow()->show(PrepareShortInfoBox(prefix)); diff --git a/Telegram/SourceFiles/media/stories/media_stories_repost_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_repost_view.cpp index 4dfdd6a56..0b8065b46 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_repost_view.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_repost_view.cpp @@ -242,19 +242,18 @@ void RepostView::recountDimensions() { auto nameFull = TextWithEntities(); nameFull.append(HistoryView::Reply::PeerEmoji(owner, _sourcePeer)); nameFull.append(name); - auto context = Core::MarkedTextContext{ + auto context = Core::TextContext({ .session = &_story->session(), - .customEmojiRepaint = [] {}, .customEmojiLoopLimit = 1, - }; + }); _name.setMarkedText( st::semiboldTextStyle, nameFull, Ui::NameTextOptions(), context); - context.customEmojiRepaint = crl::guard(this, [=] { + context.repaint = crl::guard(this, [=] { _controller->repaint(); - }), + }); _text.setMarkedText( st::defaultTextStyle, text, diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 289376216..f5cf304fd 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -3346,12 +3346,12 @@ void OverlayWidget::refreshCaption() { } update(captionGeometry()); }; - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = (_stories ? _storiesSession : &_message->history()->session()), - .customEmojiRepaint = captionRepaint, - }; + .repaint = captionRepaint, + }); _caption.setMarkedText( st::mediaviewCaptionStyle, (base.isEmpty() diff --git a/Telegram/SourceFiles/menu/menu_sponsored.cpp b/Telegram/SourceFiles/menu/menu_sponsored.cpp index a1b51ed75..231da1ee0 100644 --- a/Telegram/SourceFiles/menu/menu_sponsored.cpp +++ b/Telegram/SourceFiles/menu/menu_sponsored.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/premium_preview_box.h" #include "chat_helpers/compose/compose_show.h" +#include "core/ui_integration.h" // TextContext #include "data/components/sponsored_messages.h" #include "data/data_premium_limits.h" #include "data/data_session.h" @@ -191,11 +192,7 @@ void AboutBox( } Ui::AddSkip(content); { - const auto arrow = Ui::Text::SingleCustomEmoji( - session->data().customEmojiManager().registerInternalEmoji( - st::topicButtonArrow, - st::channelEarnLearnArrowMargins, - true)); + const auto arrow = Ui::Text::IconEmoji(&st::textMoreIconEmoji); const auto available = box->width() - rect::m::sum::h(st::boxRowPadding); box->addRow( @@ -213,7 +210,7 @@ void AboutBox( return Ui::Text::Link(std::move(t), kUrl.utf16()); }), Ui::Text::RichLangValue), - { .session = session }, + Core::TextContext({ .session = session }), st::channelEarnLearnDescription))->resizeToWidth(available); } Ui::AddSkip(content); diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index fd8468a53..05e1c0ad7 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "overview/overview_layout.h" #include "overview/overview_layout_delegate.h" -#include "core/ui_integration.h" // Core::MarkedTextContext. +#include "core/ui_integration.h" // TextContext #include "data/data_document.h" #include "data/data_document_resolver.h" #include "data/data_session.h" @@ -1042,10 +1042,10 @@ void Voice::updateName() { st::defaultTextStyle, parent()->originalText(), Ui::DialogTextOptions(), - Core::MarkedTextContext{ + Core::TextContext({ .session = &parent()->history()->session(), - .customEmojiRepaint = [=] { delegate()->repaintItem(this); }, - }); + .repaint = [=] { delegate()->repaintItem(this); }, + })); } bool Voice::updateStatusText() { diff --git a/Telegram/SourceFiles/payments/payments_reaction_process.cpp b/Telegram/SourceFiles/payments/payments_reaction_process.cpp index 94d527471..b585bab60 100644 --- a/Telegram/SourceFiles/payments/payments_reaction_process.cpp +++ b/Telegram/SourceFiles/payments/payments_reaction_process.cpp @@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_global_privacy.h" #include "apiwrap.h" #include "boxes/send_credits_box.h" // CreditsEmojiSmall. -#include "core/ui_integration.h" // MarkedTextContext. +#include "core/ui_integration.h" // TextContext. #include "data/components/credits.h" #include "data/data_channel.h" #include "data/data_message_reactions.h" @@ -186,10 +186,7 @@ void ShowPaidReactionDetails( ) | rpl::map([=](TextWithEntities &&text) { return Ui::TextWithContext{ .text = std::move(text), - .context = Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = [] {}, - }, + .context = Core::TextContext({ .session = session }), }; }); }; diff --git a/Telegram/SourceFiles/payments/ui/payments_reaction_box.h b/Telegram/SourceFiles/payments/ui/payments_reaction_box.h index 34f67e066..1468d53ba 100644 --- a/Telegram/SourceFiles/payments/ui/payments_reaction_box.h +++ b/Telegram/SourceFiles/payments/ui/payments_reaction_box.h @@ -21,7 +21,7 @@ class DynamicImage; struct TextWithContext { TextWithEntities text; - std::any context; + Text::MarkedContext context; }; struct PaidReactionTop { diff --git a/Telegram/SourceFiles/settings/business/settings_chat_links.cpp b/Telegram/SourceFiles/settings/business/settings_chat_links.cpp index e0d35efca..44025db44 100644 --- a/Telegram/SourceFiles/settings/business/settings_chat_links.cpp +++ b/Telegram/SourceFiles/settings/business/settings_chat_links.cpp @@ -164,10 +164,10 @@ Row::Row(not_null delegate, const ChatLinkData &data) } void Row::updateStatus(const ChatLinkData &data) { - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = _delegate->rowSession(), - .customEmojiRepaint = [=] { _delegate->rowUpdateRow(this); }, - }; + .repaint = [=] { _delegate->rowUpdateRow(this); }, + }); _status.setMarkedText( st::messageTextStyle, data.message, diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.cpp b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.cpp index 4b6c8024f..e8bb2607a 100644 --- a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.cpp +++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.cpp @@ -143,10 +143,7 @@ void SetupHeader( content, v::text::take_marked(std::move(about)), st, - st::defaultPopupMenu, - [=](Fn update) { - return CommonTextContext{ std::move(update) }; - })), + st::defaultPopupMenu)), st::changePhoneDescriptionPadding); wrap->setAttribute(Qt::WA_TransparentForMouseEvents); wrap->resize( diff --git a/Telegram/SourceFiles/settings/settings_business.cpp b/Telegram/SourceFiles/settings/settings_business.cpp index 82c0b9964..b1bee4942 100644 --- a/Telegram/SourceFiles/settings/settings_business.cpp +++ b/Telegram/SourceFiles/settings/settings_business.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_chat_links.h" #include "boxes/premium_preview_box.h" #include "core/click_handler_types.h" +#include "core/ui_integration.h" // TextContext #include "data/business/data_business_info.h" #include "data/business/data_business_chatbots.h" #include "data/business/data_shortcut_messages.h" @@ -526,11 +527,7 @@ void Business::setupContent() { const auto session = &_controller->session(); { - const auto arrow = Ui::Text::SingleCustomEmoji( - session->data().customEmojiManager().registerInternalEmoji( - st::topicButtonArrow, - st::channelEarnLearnArrowMargins, - true)); + const auto arrow = Ui::Text::IconEmoji(&st::textMoreIconEmoji); inner->add(object_ptr( inner, Ui::CreateLabelWithCustomEmoji( @@ -547,7 +544,7 @@ void Business::setupContent() { return Ui::Text::Link(text, url); }), Ui::Text::RichLangValue), - { .session = session }, + Core::TextContext({ .session = session }), st::boxDividerLabel), st::defaultBoxDividerLabelPadding, RectPart::Top | RectPart::Bottom)); diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 8e9c87bf3..4056c3bfb 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -469,10 +469,7 @@ SubscriptionRightLabel PaintSubscriptionRightLabelCallback( .append(QChar::Space) .append(Lang::FormatCountDecimal(amount)), kMarkupTextOptions, - Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = [] {}, - }); + Core::TextContext({ .session = session })); const auto &font = text->style()->font; const auto &statusFont = st::contactsStatusFont; const auto status = tr::lng_group_invite_joined_right(tr::now); @@ -821,10 +818,10 @@ void BoostCreditsBox( st, std::move(textWithEntities), kMarkupTextOptions, - Core::MarkedTextContext{ + Core::TextContext({ .session = session, - .customEmojiRepaint = [=] { badge->update(); }, - }); + .repaint = [=] { badge->update(); }, + })); badge->paintRequest( ) | rpl::start_with_next([=] { auto p = QPainter(badge); @@ -1328,10 +1325,10 @@ void GenericCreditsEntryBox( object_ptr( content, st::defaultTextStyle.font->height)); - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = session, - .customEmojiRepaint = [=] { amount->update(); }, - }; + .repaint = [=] { amount->update(); }, + }); if (e.soldOutInfo) { text->setText( st::defaultTextStyle, @@ -1438,11 +1435,7 @@ void GenericCreditsEntryBox( st::creditsBoxAbout))); } - const auto arrowEmoji = Ui::Text::SingleCustomEmoji( - owner->customEmojiManager().registerInternalEmoji( - st::topicButtonArrow, - st::channelEarnLearnArrowMargins, - true)); + const auto arrow = Ui::Text::IconEmoji(&st::textMoreIconEmoji); if (!uniqueGift && starGiftCanManage) { Ui::AddSkip(content); const auto about = box->addRow( @@ -1510,7 +1503,7 @@ void GenericCreditsEntryBox( Ui::AddSkip(content); auto link = tr::lng_credits_box_history_entry_gift_about_link( lt_emoji, - rpl::single(arrowEmoji), + rpl::single(arrow), Ui::Text::RichLangValue ) | rpl::map([](TextWithEntities text) { return Ui::Text::Link( @@ -1532,13 +1525,13 @@ void GenericCreditsEntryBox( lt_link, std::move(link), Ui::Text::RichLangValue), - { .session = session }, + Core::TextContext({ .session = session }), st::creditsBoxAbout))); } else if (e.paidMessagesCommission && e.barePeerId) { Ui::AddSkip(content); auto link = tr::lng_credits_paid_messages_fee_about_link( lt_emoji, - rpl::single(arrowEmoji), + rpl::single(arrow), Ui::Text::RichLangValue ) | rpl::map([id = e.barePeerId](TextWithEntities text) { return Ui::Text::Link( @@ -1557,7 +1550,7 @@ void GenericCreditsEntryBox( lt_link, std::move(link), Ui::Text::RichLangValue), - { .session = session }, + Core::TextContext({ .session = session }), st::creditsBoxAbout))); } @@ -2475,10 +2468,6 @@ void AddWithdrawalWidget( st::settingsPremiumIconStar, { 0, -st::moderateBoxExpandInnerSkip, 0, 0 }, true)); - const auto context = Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = [=] { label->update(); }, - }; using Balance = rpl::variable; const auto currentBalance = input->lifetime().make_state( rpl::duplicate(availableBalanceValue)); @@ -2496,7 +2485,7 @@ void AddWithdrawalWidget( lt_emoji, buttonEmoji, Ui::Text::RichLangValue), - context); + Core::TextContext({ .session = session })); } }; QObject::connect(input, &Ui::MaskedInputField::changed, process); @@ -2564,10 +2553,10 @@ void AddWithdrawalWidget( constexpr auto kDateUpdateInterval = crl::time(250); const auto was = base::unixtime::serialize(dt); - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = session, - .customEmojiRepaint = [=] { lockedLabel->update(); }, - }; + .repaint = [=] { lockedLabel->update(); }, + }); const auto emoji = Ui::Text::SingleCustomEmoji( session->data().customEmojiManager().registerInternalEmoji( st::chatSimilarLockedIcon, @@ -2644,11 +2633,7 @@ void AddWithdrawalWidget( Ui::AddSkip(container); Ui::AddSkip(container); - const auto arrow = Ui::Text::SingleCustomEmoji( - session->data().customEmojiManager().registerInternalEmoji( - st::topicButtonArrow, - st::channelEarnLearnArrowMargins, - true)); + const auto arrow = Ui::Text::IconEmoji(&st::textMoreIconEmoji); auto about = Ui::CreateLabelWithCustomEmoji( container, tr::lng_bot_earn_learn_credits_out_about( @@ -2663,7 +2648,7 @@ void AddWithdrawalWidget( tr::lng_bot_earn_balance_about_url(tr::now)); }), Ui::Text::RichLangValue), - { .session = session }, + Core::TextContext({ .session = session }), st::boxDividerLabel); Ui::AddSkip(container); container->add(object_ptr( diff --git a/Telegram/SourceFiles/settings/settings_folders.cpp b/Telegram/SourceFiles/settings/settings_folders.cpp index ddcc9071c..995591894 100644 --- a/Telegram/SourceFiles/settings/settings_folders.cpp +++ b/Telegram/SourceFiles/settings/settings_folders.cpp @@ -199,11 +199,11 @@ void FilterRowButton::updateData( st::contactsNameStyle, title.text, kMarkupTextOptions, - Core::MarkedTextContext{ + Core::TextContext({ .session = _session, - .customEmojiRepaint = [=] { update(); }, + .repaint = [=] { update(); }, .customEmojiLoopLimit = title.isStatic ? -1 : 0, - }); + })); _icon = Ui::ComputeFilterIcon(filter); _colorIndex = filter.colorIndex(); if (!ignoreCount) { diff --git a/Telegram/SourceFiles/settings/settings_premium.cpp b/Telegram/SourceFiles/settings/settings_premium.cpp index 343bbefc2..a834f9a6d 100644 --- a/Telegram/SourceFiles/settings/settings_premium.cpp +++ b/Telegram/SourceFiles/settings/settings_premium.cpp @@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "core/click_handler_types.h" #include "core/local_url_handlers.h" // Core::TryConvertUrlToLocal. -#include "core/ui_integration.h" // MarkedTextContext. +#include "core/ui_integration.h" // TextContext. #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_emoji_statuses.h" @@ -788,11 +788,9 @@ void TopBarUser::updateTitle( lt_link, { .text = text, .entities = entities, }, Ui::Text::WithEntities); - const auto context = Core::MarkedTextContext{ - .session = &controller->session(), - .customEmojiRepaint = [=] { _title->update(); }, - }; - _title->setMarkedText(std::move(title), context); + _title->setMarkedText( + std::move(title), + Core::TextContext({ .session = &controller->session() })); auto link = std::make_shared([=, stickerSetIdentifier = stickerInfo->set] { setPaused(true); diff --git a/Telegram/SourceFiles/ui/boxes/collectible_info_box.cpp b/Telegram/SourceFiles/ui/boxes/collectible_info_box.cpp index d76d6802d..fb0300664 100644 --- a/Telegram/SourceFiles/ui/boxes/collectible_info_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/collectible_info_box.cpp @@ -224,7 +224,9 @@ void CollectibleInfoBox( object_ptr(box, st::collectibleInfo), st::collectibleInfoPadding); label->setAttribute(Qt::WA_TransparentForMouseEvents); - label->setMarkedText(text, details.tonEmojiContext()); + auto context = details.tonEmojiContext; + context.repaint = [label] { label->update(); }; + label->setMarkedText(text, context); const auto more = box->addRow( object_ptr( diff --git a/Telegram/SourceFiles/ui/boxes/collectible_info_box.h b/Telegram/SourceFiles/ui/boxes/collectible_info_box.h index 8b80b9650..9080819c9 100644 --- a/Telegram/SourceFiles/ui/boxes/collectible_info_box.h +++ b/Telegram/SourceFiles/ui/boxes/collectible_info_box.h @@ -34,7 +34,7 @@ struct CollectibleInfo { struct CollectibleDetails { TextWithEntities tonEmoji; - Fn tonEmojiContext; + Text::MarkedContext tonEmojiContext; }; void CollectibleInfoBox( diff --git a/Telegram/SourceFiles/ui/boxes/edit_invite_link_session.cpp b/Telegram/SourceFiles/ui/boxes/edit_invite_link_session.cpp index 8c1a68b20..f868cc3e9 100644 --- a/Telegram/SourceFiles/ui/boxes/edit_invite_link_session.cpp +++ b/Telegram/SourceFiles/ui/boxes/edit_invite_link_session.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/boxes/edit_invite_link_session.h" +#include "core/ui_integration.h" // TextContext #include "data/components/credits.h" #include "data/data_peer.h" #include "data/data_session.h" @@ -128,18 +129,13 @@ InviteLinkSubscriptionToggle FillCreateInviteLinkSubscriptionToggle( state->usdRate = peer->session().credits().rateValue(peer); - const auto arrow = Ui::Text::SingleCustomEmoji( - peer->owner().customEmojiManager().registerInternalEmoji( - st::topicButtonArrow, - st::channelEarnLearnArrowMargins, - true)); auto about = Ui::CreateLabelWithCustomEmoji( container, tr::lng_group_invite_subscription_about( lt_link, tr::lng_group_invite_subscription_about_link( lt_emoji, - rpl::single(arrow), + rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)), Ui::Text::RichLangValue ) | rpl::map([](TextWithEntities text) { return Ui::Text::Link( @@ -147,7 +143,7 @@ InviteLinkSubscriptionToggle FillCreateInviteLinkSubscriptionToggle( tr::lng_group_invite_subscription_about_url(tr::now)); }), Ui::Text::RichLangValue), - { .session = &peer->session() }, + Core::TextContext({ .session = &peer->session() }), st::boxDividerLabel); Ui::AddSkip(wrap->entity()); Ui::AddSkip(wrap->entity()); diff --git a/Telegram/SourceFiles/ui/chat/chats_filter_tag.cpp b/Telegram/SourceFiles/ui/chat/chats_filter_tag.cpp index 077de4f32..020ffa4b0 100644 --- a/Telegram/SourceFiles/ui/chat/chats_filter_tag.cpp +++ b/Telegram/SourceFiles/ui/chat/chats_filter_tag.cpp @@ -174,7 +174,6 @@ bool ScaledCustomEmoji::readyInDefaultState() { ChatsFilterTagContext &context) { auto i = text.entities.begin(); auto ch = text.text.constData(); - auto &integration = Integration::Instance(); context.loading = false; const auto end = text.text.constData() + text.text.size(); const auto adjust = [&](EntityInText &entity) { @@ -187,9 +186,7 @@ bool ScaledCustomEmoji::readyInDefaultState() { } auto &emoji = context.emoji[data]; if (!emoji) { - emoji = integration.createCustomEmoji( - data, - context.textContext); + emoji = Text::MakeCustomEmoji(data, context.textContext); } if (!emoji->ready()) { context.loading = true; diff --git a/Telegram/SourceFiles/ui/chat/chats_filter_tag.h b/Telegram/SourceFiles/ui/chat/chats_filter_tag.h index 28da74c80..8291b5e33 100644 --- a/Telegram/SourceFiles/ui/chat/chats_filter_tag.h +++ b/Telegram/SourceFiles/ui/chat/chats_filter_tag.h @@ -8,16 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "emoji.h" - -namespace Ui::Text { -class CustomEmoji; -} // namespace Ui::Text +#include "ui/text/text.h" namespace Ui { struct ChatsFilterTagContext { base::flat_map> emoji; - std::any textContext; + Text::MarkedContext textContext; QColor color; bool active = false; bool loading = false; diff --git a/Telegram/SourceFiles/ui/chat/message_bar.h b/Telegram/SourceFiles/ui/chat/message_bar.h index 36b96658e..6132ae446 100644 --- a/Telegram/SourceFiles/ui/chat/message_bar.h +++ b/Telegram/SourceFiles/ui/chat/message_bar.h @@ -26,7 +26,7 @@ struct MessageBarContent { int count = 1; QString title; TextWithEntities text; - std::any context; + Text::MarkedContext context; QImage preview; Fn spoilerRepaint; style::margins margins; diff --git a/Telegram/SourceFiles/ui/chat/sponsored_message_bar.cpp b/Telegram/SourceFiles/ui/chat/sponsored_message_bar.cpp index adce1fcd7..1f2fd2417 100644 --- a/Telegram/SourceFiles/ui/chat/sponsored_message_bar.cpp +++ b/Telegram/SourceFiles/ui/chat/sponsored_message_bar.cpp @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "core/click_handler_types.h" -#include "core/ui_integration.h" // Core::MarkedTextContext. +#include "core/ui_integration.h" // TextContext #include "data/components/sponsored_messages.h" #include "data/data_session.h" #include "history/history_item_helpers.h" @@ -202,10 +202,10 @@ void FillSponsoredMessageBar( contentTextSt, textWithEntities, kMarkupTextOptions, - Core::MarkedTextContext{ + Core::TextContext({ .session = session, - .customEmojiRepaint = [=] { widget->update(); }, - }); + .repaint = [=] { widget->update(); }, + })); const auto hostedClick = [=](ClickHandlerPtr handler) { return [=] { if (const auto controller = FindSessionController(widget)) { diff --git a/Telegram/SourceFiles/ui/controls/filter_link_header.cpp b/Telegram/SourceFiles/ui/controls/filter_link_header.cpp index 16c6cf398..e6a02ef66 100644 --- a/Telegram/SourceFiles/ui/controls/filter_link_header.cpp +++ b/Telegram/SourceFiles/ui/controls/filter_link_header.cpp @@ -77,7 +77,7 @@ private: QPainterPath _titlePath; TextWithEntities _folderTitle; - Fn)> _makeContext; + Text::MarkedContext _aboutContext; not_null _folderIcon; bool _horizontalFilters = false; @@ -90,7 +90,7 @@ private: [[nodiscard]] PreviewState GeneratePreview( not_null parent, const TextWithEntities &title, - Fn)> makeContext, + Text::MarkedContext aboutContext, int badge) { using Tabs = Ui::ChatsFiltersTabs; auto preview = PreviewState(); @@ -126,14 +126,14 @@ private: return state->cache; }; const auto raw = &state->tabs; - const auto repaint = [=] { - state->dirty = true; - }; + const auto repaint = [=] { state->dirty = true; }; + auto context = aboutContext; + context.repaint = repaint; raw->setSections({ TextWithEntities{ tr::lng_filters_name_people(tr::now) }, title, TextWithEntities{ tr::lng_filters_name_unread(tr::now) }, - }, makeContext(repaint)); + }, context); raw->fitWidthToSections(); raw->setActiveSectionFast(1); raw->stopAnimation(); @@ -153,7 +153,7 @@ private: [[nodiscard]] PreviewState GeneratePreview( const TextWithEntities &title, - Fn)> makeContext, + Text::MarkedContext context, not_null icon, int badge) { auto preview = PreviewState(); @@ -165,7 +165,7 @@ private: bool dirty = true; }; const auto state = preview.lifetime.make_state(); - const auto repaint = [=] { + context.repaint = [=] { state->dirty = true; }; @@ -201,7 +201,7 @@ private: text, kMarkupTextOptions, available, - makeContext(repaint)); + context); }; const auto paintName = [=](QPainter &p, int top) { state->string.draw(p, { @@ -299,7 +299,7 @@ Widget::Widget( rpl::single(descriptor.about.value()), st::filterLinkAbout, st::defaultPopupMenu, - descriptor.makeAboutContext)) + descriptor.aboutContext)) , _close(CreateChild(this, st::boxTitleClose)) , _aboutPadding(st::boxRowPadding) , _badge(std::move(descriptor.badge)) @@ -307,7 +307,7 @@ Widget::Widget( , _titleFont(st::boxTitle.style.font) , _titlePadding(st::filterLinkTitlePadding) , _folderTitle(descriptor.folderTitle) -, _makeContext(descriptor.makeAboutContext) +, _aboutContext(descriptor.aboutContext) , _folderIcon(descriptor.folderIcon) , _horizontalFilters(descriptor.horizontalFilters) { setMinimumHeight(st::boxTitleHeight); @@ -417,20 +417,24 @@ void Widget::paintEvent(QPaintEvent *e) { auto hq = PainterHighQualityEnabler(p); if (!_preview.frame) { const auto badge = _badge.current(); - const auto makeContext = [=](Fn repaint) { - return _makeContext([=] { repaint(); update(); }); + auto context = _aboutContext; + context.repaint = [this, copy = context.repaint] { + if (const auto &repaint = copy) { + repaint(); + } + update(); }; if (_horizontalFilters) { _preview = GeneratePreview( this, _folderTitle, - makeContext, + context, badge); Widget::resizeEvent(nullptr); } else { _preview = GeneratePreview( _folderTitle, - makeContext, + context, _folderIcon, badge); } @@ -486,7 +490,7 @@ object_ptr FilterLinkProcessButton( not_null parent, FilterLinkHeaderType type, TextWithEntities title, - Fn)> makeContext, + Text::MarkedContext context, rpl::producer badge) { const auto st = &st::filterInviteBox.button; const auto badgeSt = &st::filterInviteButtonBadgeStyle; @@ -600,12 +604,13 @@ object_ptr FilterLinkProcessButton( } }, label->lifetime()); + context.repaint = [=] { label->update(); }; std::move(data) | rpl::start_with_next([=](Data data) { label->text.setMarkedText( st::filterInviteButtonStyle, data.text, kMarkupTextOptions, - makeContext([=] { label->update(); })); + context); label->badge.setText(st::filterInviteButtonBadgeStyle, data.badge); label->update(); }, label->lifetime()); diff --git a/Telegram/SourceFiles/ui/controls/filter_link_header.h b/Telegram/SourceFiles/ui/controls/filter_link_header.h index 55d092387..a530de23a 100644 --- a/Telegram/SourceFiles/ui/controls/filter_link_header.h +++ b/Telegram/SourceFiles/ui/controls/filter_link_header.h @@ -26,7 +26,7 @@ struct FilterLinkHeaderDescriptor { base::required type; base::required title; base::required about; - Fn)> makeAboutContext; + Text::MarkedContext aboutContext; base::required folderTitle; not_null folderIcon; rpl::producer badge; @@ -47,7 +47,7 @@ struct FilterLinkHeader { not_null parent, FilterLinkHeaderType type, TextWithEntities title, - Fn)> makeContext, + Text::MarkedContext context, rpl::producer badge); } // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/tabbed_search.cpp b/Telegram/SourceFiles/ui/controls/tabbed_search.cpp index 59a1f6cf0..ea9f1e70f 100644 --- a/Telegram/SourceFiles/ui/controls/tabbed_search.cpp +++ b/Telegram/SourceFiles/ui/controls/tabbed_search.cpp @@ -149,7 +149,7 @@ void GroupsStrip::set(std::vector list) { .icon = std::make_unique( _factory( group.iconId, - updater(group.iconId)), + { .repaint = updater(group.iconId) }), loopCount, stopAtLastFrame), }); diff --git a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp index 9834489b1..3fe236355 100644 --- a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp +++ b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp @@ -359,7 +359,7 @@ void Action::paint(Painter &p) { if (!_custom && !_content.singleCustomEntityData.isEmpty()) { _custom = _customEmojiFactory( _content.singleCustomEntityData, - [=] { update(); }); + { .repaint = [=] { update(); } }); } if (_custom) { const auto ratio = style::DevicePixelRatio(); @@ -772,7 +772,9 @@ void WhoReactedEntryAction::setData(Data &&data) { } _type = data.type; _custom = _customEmojiFactory - ? _customEmojiFactory(data.customEntityData, [=] { update(); }) + ? _customEmojiFactory( + data.customEntityData, + { .repaint = [=] { update(); } }) : nullptr; const auto ratio = style::DevicePixelRatio(); const auto size = Emoji::GetSizeNormal() / ratio; diff --git a/Telegram/SourceFiles/ui/unread_badge.cpp b/Telegram/SourceFiles/ui/unread_badge.cpp index b01dcb198..233ff51a9 100644 --- a/Telegram/SourceFiles/ui/unread_badge.cpp +++ b/Telegram/SourceFiles/ui/unread_badge.cpp @@ -298,7 +298,7 @@ void PeerBadge::set( if (details->iconId) { _botVerifiedData->icon = factory( Data::SerializeCustomEmojiId(details->iconId), - repaint); + { .repaint = repaint }); } } diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp index 8149ffca5..5fddc9e79 100644 --- a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp +++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp @@ -54,7 +54,7 @@ ChatsFiltersTabs::ChatsFiltersTabs( bool ChatsFiltersTabs::setSectionsAndCheckChanged( std::vector &§ions, - const std::any &context, + const Text::MarkedContext &context, Fn paused) { const auto &was = sectionsRef(); const auto changed = [&] { diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h index 64c66c062..1375b70dc 100644 --- a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h +++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h @@ -29,7 +29,7 @@ public: bool setSectionsAndCheckChanged( std::vector &§ions, - const std::any &context, + const Text::MarkedContext &context, Fn paused); void fitWidthToSections() override; diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp index 64cd5eb39..176b5644c 100644 --- a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp +++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp @@ -175,11 +175,11 @@ void ShowFiltersListMenu( icon); action->setEnabled(i < premiumFrom); if (!title.text.empty()) { - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = session, - .customEmojiRepaint = [raw = item.get()] { raw->update(); }, + .repaint = [raw = item.get()] { raw->update(); }, .customEmojiLoopLimit = title.isStatic ? -1 : 0, - }; + }); item->setMarkedText(title.text, QString(), context); } state->menu->addAction(std::move(item)); @@ -344,10 +344,7 @@ not_null AddChatFiltersTabsStrip( if ((list.size() <= 1 && !slider->width()) || state->ignoreRefresh) { return; } - const auto context = Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = [=] { slider->update(); }, - }; + const auto context = Core::TextContext({ .session = session }); const auto paused = [=] { return On(PowerSaving::kEmojiChat) || controller->isGifPausedAtLeastFor(pauseLevel); diff --git a/Telegram/SourceFiles/ui/widgets/discrete_sliders.cpp b/Telegram/SourceFiles/ui/widgets/discrete_sliders.cpp index e11ea96d0..77874b58d 100644 --- a/Telegram/SourceFiles/ui/widgets/discrete_sliders.cpp +++ b/Telegram/SourceFiles/ui/widgets/discrete_sliders.cpp @@ -79,7 +79,8 @@ void DiscreteSlider::addSection(const QString &label) { void DiscreteSlider::addSection( const TextWithEntities &label, - const std::any &context) { + Text::MarkedContext context) { + context.repaint = [this] { update(); }; _sections.push_back(Section(label, getLabelStyle(), context)); resizeToWidth(width()); } @@ -96,9 +97,11 @@ void DiscreteSlider::setSections(const std::vector &labels) { void DiscreteSlider::setSections( const std::vector &labels, - const std::any &context) { + Text::MarkedContext context) { Assert(!labels.empty()); + context.repaint = [this] { update(); }; + _sections.clear(); for (const auto &label : labels) { _sections.push_back(Section(label, getLabelStyle(), context)); @@ -225,7 +228,7 @@ DiscreteSlider::Section::Section( DiscreteSlider::Section::Section( const TextWithEntities &label, const style::TextStyle &st, - const std::any &context) { + const Text::MarkedContext &context) { this->label.setMarkedText(st, label, kMarkupTextOptions, context); contentWidth = Section::label.maxWidth(); } diff --git a/Telegram/SourceFiles/ui/widgets/discrete_sliders.h b/Telegram/SourceFiles/ui/widgets/discrete_sliders.h index 2ff6f771c..4e9476bb0 100644 --- a/Telegram/SourceFiles/ui/widgets/discrete_sliders.h +++ b/Telegram/SourceFiles/ui/widgets/discrete_sliders.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rp_widget.h" #include "ui/round_rect.h" #include "ui/effects/animations.h" +#include "ui/text/text.h" namespace style { struct TextStyle; @@ -32,11 +33,11 @@ public: void addSection(const QString &label); void addSection( const TextWithEntities &label, - const std::any &context = {}); + Text::MarkedContext context = {}); void setSections(const std::vector &labels); void setSections( const std::vector &labels, - const std::any &context = {}); + Text::MarkedContext context = {}); int activeSection() const { return _activeIndex; } @@ -63,9 +64,9 @@ protected: Section( const TextWithEntities &label, const style::TextStyle &st, - const std::any &context); + const Text::MarkedContext &context); - Ui::Text::String label; + Text::String label; std::unique_ptr ripple; int left = 0; int width = 0; diff --git a/Telegram/SourceFiles/ui/widgets/label_with_custom_emoji.cpp b/Telegram/SourceFiles/ui/widgets/label_with_custom_emoji.cpp index 4502d6553..69f1d5f14 100644 --- a/Telegram/SourceFiles/ui/widgets/label_with_custom_emoji.cpp +++ b/Telegram/SourceFiles/ui/widgets/label_with_custom_emoji.cpp @@ -16,13 +16,12 @@ namespace Ui { object_ptr CreateLabelWithCustomEmoji( QWidget *parent, rpl::producer &&text, - Core::MarkedTextContext context, + Text::MarkedContext context, const style::FlatLabel &st) { auto label = object_ptr(parent, st); const auto raw = label.data(); - if (!context.customEmojiRepaint) { - context.customEmojiRepaint = [=] { raw->update(); }; - } + + context.repaint = [=] { raw->update(); }; std::move(text) | rpl::start_with_next([=](const TextWithEntities &text) { raw->setMarkedText(text, context); }, label->lifetime()); diff --git a/Telegram/SourceFiles/ui/widgets/label_with_custom_emoji.h b/Telegram/SourceFiles/ui/widgets/label_with_custom_emoji.h index f22e4e801..662ce0fa7 100644 --- a/Telegram/SourceFiles/ui/widgets/label_with_custom_emoji.h +++ b/Telegram/SourceFiles/ui/widgets/label_with_custom_emoji.h @@ -7,8 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "core/ui_integration.h" // Core::MarkedTextContext. - template class object_ptr; @@ -20,16 +18,18 @@ namespace style { struct FlatLabel; } // namespace style -namespace Ui { -class FlatLabel; -} // namespace Ui +namespace Ui::Text { +struct MarkedContext; +} // namespace Ui::Text namespace Ui { +class FlatLabel; + [[nodiscard]] object_ptr CreateLabelWithCustomEmoji( QWidget *parent, rpl::producer &&text, - Core::MarkedTextContext context, + Text::MarkedContext context, const style::FlatLabel &st); } // namespace Ui diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index 19d557e72..8a8ccea36 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -949,10 +949,10 @@ void Notification::updateNotifyDisplay() { 0, Qt::LayoutDirectionAuto, }; - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &_history->session(), - .customEmojiRepaint = [=] { customEmojiCallback(); }, - }; + .repaint = [=] { customEmojiCallback(); }, + }); _textCache.setMarkedText( st::dialogsTextStyle, text, @@ -988,10 +988,10 @@ void Notification::updateNotifyDisplay() { const auto fullTitle = manager()->addTargetAccountName( std::move(title), &_history->session()); - const auto context = Core::MarkedTextContext{ + const auto context = Core::TextContext({ .session = &_history->session(), - .customEmojiRepaint = [=] { customEmojiCallback(); }, - }; + .repaint = [=] { customEmojiCallback(); }, + }); _titleCache.setMarkedText( st::semiboldTextStyle, fullTitle, diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index 829a84856..1e71a84b7 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -253,13 +253,6 @@ base::unique_qptr FiltersMenu::prepareButton( Ui::FilterIcon icon, bool toBeginning) { const auto isStatic = title.isStatic; - const auto makeContext = [=](Fn update) { - return Core::MarkedTextContext{ - .session = &_session->session(), - .customEmojiRepaint = std::move(update), - .customEmojiLoopLimit = isStatic ? -1 : 0, - }; - }; const auto paused = [=] { return On(PowerSaving::kEmojiChat) || _session->isGifPausedAtLeastFor(Window::GifPauseReason::Any); @@ -268,7 +261,10 @@ base::unique_qptr FiltersMenu::prepareButton( container, id ? title.text : TextWithEntities{ tr::lng_filters_all(tr::now) }, st::windowFiltersButton, - makeContext, + Core::TextContext({ + .session = &_session->session(), + .customEmojiLoopLimit = isStatic ? -1 : 0, + }), paused); auto added = toBeginning ? container->insert(0, std::move(prepared)) diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index cf75dc76d..18783480e 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -172,12 +172,6 @@ private: [[nodiscard]] Ui::CollectibleDetails PrepareCollectibleDetails( not_null session) { - const auto makeContext = [=] { - return Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = [] {}, - }; - }; return { .tonEmoji = Ui::Text::SingleCustomEmoji( session->data().customEmojiManager().registerInternalEmoji( @@ -186,7 +180,7 @@ private: st::collectibleInfo.textFg->c), st::collectibleInfoTonMargins, true)), - .tonEmojiContext = makeContext, + .tonEmojiContext = Core::TextContext({ .session = session }), }; } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 76f254814..e51fe382b 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 76f25481470faee2e0a24aa7be8c6d58564addea +Subproject commit e51fe382bda9f2944412078da70f04de5dd821f3 From ab58e7a2253e721d0ad043a9319a2746d425901d Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Mar 2025 17:25:06 +0400 Subject: [PATCH 093/103] Show common groups userpics in new-peer-info. --- .../SourceFiles/core/local_url_handlers.cpp | 26 ++ .../history/history_inner_widget.cpp | 3 + .../history/view/history_view_about_view.cpp | 236 +++++++++++++++++- .../history/view/history_view_about_view.h | 12 + .../view/media/history_view_unique_gift.cpp | 20 ++ .../view/media/history_view_unique_gift.h | 4 + Telegram/SourceFiles/ui/chat/chat.style | 7 + Telegram/SourceFiles/ui/unread_badge.cpp | 7 +- Telegram/lib_ui | 2 +- 9 files changed, 304 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index adc083d79..8c5b3b7d2 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -69,6 +69,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_domain.h" #include "main/main_session.h" #include "main/main_session_settings.h" +#include "info/info_controller.h" +#include "info/info_memento.h" #include "inline_bots/bot_attach_web_view.h" #include "history/history.h" #include "history/history_item.h" @@ -1034,6 +1036,26 @@ bool EditPaidMessagesFee( return true; } +bool ShowCommonGroups( + Window::SessionController *controller, + const Match &match, + const QVariant &context) { + if (!controller) { + return false; + } + const auto peerId = PeerId(match->captured(1).toULongLong()); + if (const auto id = peerToUser(peerId)) { + const auto user = controller->session().data().userLoaded(id); + if (user) { + controller->showSection( + std::make_shared( + user, + Info::Section::Type::CommonGroups)); + } + } + return true; +} + bool ShowStarsExamples( Window::SessionController *controller, const Match &match, @@ -1541,6 +1563,10 @@ const std::vector &InternalUrlHandlers() { u"^edit_paid_messages_fee/([0-9]+)$"_q, EditPaidMessagesFee, }, + { + u"^common_groups/([0-9]+)$"_q, + ShowCommonGroups, + }, { u"^stars_examples$"_q, ShowStarsExamples, diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 983ec2839..362d5ef8b 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -4342,6 +4342,9 @@ void HistoryInner::refreshAboutView(bool force) { _aboutView = std::make_unique( _history, _history->delegateMixin()->delegate()); + _aboutView->refreshRequests() | rpl::start_with_next([=] { + updateBotInfo(); + }, _aboutView->lifetime()); } }; if (const auto user = _peer->asUser()) { diff --git a/Telegram/SourceFiles/history/view/history_view_about_view.cpp b/Telegram/SourceFiles/history/view/history_view_about_view.cpp index ca6eb9a83..c1f9346b5 100644 --- a/Telegram/SourceFiles/history/view/history_view_about_view.cpp +++ b/Telegram/SourceFiles/history/view/history_view_about_view.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_session.h" #include "data/data_user.h" +#include "history/view/history_view_group_call_bar.h" #include "history/view/media/history_view_media_generic.h" #include "history/view/media/history_view_service_box.h" #include "history/view/media/history_view_sticker_player_abstract.h" @@ -37,17 +38,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_credits.h" // BuyStarsHandler #include "settings/settings_premium.h" #include "ui/chat/chat_style.h" +#include "ui/text/custom_emoji_instance.h" #include "ui/text/text_utilities.h" #include "ui/text/text_options.h" +#include "ui/dynamic_image.h" #include "ui/painter.h" #include "window/window_session_controller.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" // GroupCallUserpics #include "styles/style_credits.h" namespace HistoryView { namespace { constexpr auto kLabelOpacity = 0.85; +constexpr auto kMaxCommonChatsUserpics = 3; class EmptyChatLockedBox final : public ServiceBoxContent @@ -95,6 +100,101 @@ private: }; +class UserpicsList final : public Ui::DynamicImage { +public: + UserpicsList( + std::vector> peers, + const style::GroupCallUserpics &st, + int countOverride = 0); + + [[nodiscard]] int width() const; + + std::shared_ptr clone() override; + + QImage image(int size) override; + void subscribeToUpdates(Fn callback) override; + +private: + struct Subscribed { + explicit Subscribed(Fn callback) + : callback(std::move(callback)) { + } + + std::vector list; + bool someNotLoaded = false; + Fn callback; + int paletteVersion = 0; + }; + + const std::vector> _peers; + const style::GroupCallUserpics &_st; + const int _countOverride = 0; + + QImage _frame; + std::unique_ptr _subscribed; + +}; + +UserpicsList::UserpicsList( + std::vector> peers, + const style::GroupCallUserpics &st, + int countOverride) +: _peers(std::move(peers)) +, _st(st) +, _countOverride(countOverride) { +} + +std::shared_ptr UserpicsList::clone() { + return std::make_shared(_peers, _st); +} + +QImage UserpicsList::image(int size) { + Expects(_subscribed != nullptr); + + const auto regenerate = [&] { + const auto version = style::PaletteVersion(); + if (_frame.isNull() || _subscribed->paletteVersion != version) { + _subscribed->paletteVersion = version; + return true; + } + for (auto &entry : _subscribed->list) { + const auto peer = entry.peer; + auto &view = entry.view; + const auto wasView = view.cloud.get(); + if (peer->userpicUniqueKey(view) != entry.uniqueKey + || view.cloud.get() != wasView) { + return true; + } + } + return false; + }(); + if (regenerate) { + const auto max = std::max(_countOverride, int(_peers.size())); + GenerateUserpicsInRow(_frame, _subscribed->list, _st, max); + } + return _frame; +} + +void UserpicsList::subscribeToUpdates(Fn callback) { + if (!callback) { + _subscribed = nullptr; + return; + } + _subscribed = std::make_unique(std::move(callback)); + for (const auto peer : _peers) { + _subscribed->list.push_back({ .peer = peer }); + } +} + +int UserpicsList::width() const { + const auto count = std::max(_countOverride, int(_peers.size())); + if (!count) { + return 0; + } + const auto shifted = count - 1; + return _st.size + (shifted * (_st.size - _st.shift)); +} + auto GenerateChatIntro( not_null parent, Element *replacing, @@ -165,7 +265,8 @@ auto GenerateChatIntro( auto GenerateNewPeerInfo( not_null parent, Element *replacing, - not_null user) + not_null user, + std::vector> commonGroups) -> Fn, Fn)>)> { @@ -208,9 +309,22 @@ auto GenerateNewPeerInfo( }); } + const auto context = Core::TextContext({ + .session = &parent->history()->session(), + .repaint = [parent] { parent->repaint(); }, + }); + const auto kUserpicsPrefix = u"userpics-list/"_q; if (const auto count = user->commonChatsCount()) { const auto url = u"internal:common_groups/"_q + QString::number(user->id.value); + auto ids = QStringList(); + const auto userpics = std::min(count, kMaxCommonChatsUserpics); + for (auto i = 0; i != userpics; ++i) { + ids.push_back(QString::number(i < commonGroups.size() + ? commonGroups[i]->id.value + : 0)); + } + auto userpicsData = kUserpicsPrefix + ids.join(','); entries.push_back({ tr::lng_new_contact_common_groups(tr::now), Ui::Text::Wrapped( @@ -219,7 +333,7 @@ auto GenerateNewPeerInfo( lt_count, count, lt_emoji, - TextWithEntities(), + Ui::Text::SingleCustomEmoji(userpicsData), lt_arrow, Ui::Text::IconEmoji(&st::textMoreIconEmoji), Ui::Text::Bold), @@ -228,16 +342,40 @@ auto GenerateNewPeerInfo( }); } + auto copy = context; + copy.customEmojiFactory = [=, old = copy.customEmojiFactory]( + QStringView data, + const Ui::Text::MarkedContext &context + ) -> std::unique_ptr { + if (!data.startsWith(kUserpicsPrefix)) { + return old(data, context); + } + const auto ids = data.mid(kUserpicsPrefix.size()).split(','); + auto peers = std::vector>(); + for (const auto &id : ids) { + if (const auto peerId = PeerId(id.toULongLong())) { + peers.push_back(user->owner().peer(peerId)); + } + } + auto image = std::make_shared( + std::move(peers), + st::newPeerUserpics, + ids.size()); + const auto size = image->width(); + return std::make_unique( + data.toString(), + std::move(image), + context.repaint, + st::newPeerUserpicsPadding, + size); + }; push(std::make_unique( std::move(entries), st::newPeerSubtitleMargin, fadedFg, - normalFg)); + normalFg, + copy)); - const auto context = Core::TextContext({ - .session = &parent->history()->session(), - .repaint = [parent] { parent->repaint(); }, - }); const auto details = user->botVerifyDetails(); const auto text = details ? Data::SingleCustomEmoji( @@ -380,9 +518,10 @@ bool AboutView::refresh() { if (user && !user->isContact() && !user->phoneCountryCode().isEmpty()) { - if (_item) { + if (_item && !_commonGroupsStale) { return false; } + loadCommonGroups(); setItem(makeNewPeerInfo(user), nullptr); return true; } else if (user && !user->isSelf() && _history->isDisplayedEmpty()) { @@ -474,6 +613,14 @@ void AboutView::make(Data::ChatIntro data, bool preview) { setItem(std::move(owned), data.sticker); } +rpl::producer<> AboutView::refreshRequests() const { + return _refreshRequests.events(); +} + +rpl::lifetime &AboutView::lifetime() { + return _lifetime; +} + void AboutView::toggleStickerRegistered(bool registered) { if (const auto item = _item ? _item->data().get() : nullptr) { if (_sticker) { @@ -490,6 +637,75 @@ void AboutView::toggleStickerRegistered(bool registered) { } } +void AboutView::loadCommonGroups() { + if (_commonGroupsRequested) { + return; + } + _commonGroupsRequested = true; + + const auto user = _history->peer->asUser(); + if (!user) { + return; + } + + struct Cached { + std::vector> list; + }; + struct Session { + base::flat_map, Cached> data; + }; + static auto Map = base::flat_map, Session>(); + const auto session = &_history->session(); + auto i = Map.find(session); + if (i == end(Map)) { + i = Map.emplace(session).first; + session->lifetime().add([session] { + Map.remove(session); + }); + } + auto &cached = i->second.data[user]; + + const auto count = user->commonChatsCount(); + if (!count) { + cached = {}; + return; + } else while (cached.list.size() > count) { + cached.list.pop_back(); + } + _commonGroups = cached.list; + const auto requestId = _history->session().api().request( + MTPmessages_GetCommonChats( + user->inputUser, + MTP_long(0), + MTP_int(kMaxCommonChatsUserpics)) + ).done([=](const MTPmessages_Chats &result) { + const auto chats = result.match([](const auto &data) { + return &data.vchats().v; + }); + auto &owner = user->session().data(); + auto list = std::vector>(); + list.reserve(chats->size()); + for (const auto &chat : *chats) { + if (const auto peer = owner.processChat(chat)) { + list.push_back(peer); + if (list.size() == kMaxCommonChatsUserpics) { + break; + } + } + } + if (_commonGroups != list) { + Map[session].data[user].list = list; + _commonGroups = std::move(list); + _commonGroupsStale = true; + _refreshRequests.fire({}); + } + }).send(); + + _lifetime.add([=] { + _history->session().api().request(requestId).cancel(); + }); +} + void AboutView::setHelloChosen(not_null sticker) { _helloChosen = sticker; toggleStickerRegistered(false); @@ -505,6 +721,8 @@ void AboutView::setItem(AdminLog::OwnedItem item, DocumentData *sticker) { } AdminLog::OwnedItem AboutView::makeNewPeerInfo(not_null user) { + _commonGroupsStale = false; + const auto text = user->name(); const auto item = _history->makeMessage({ .id = _history->nextNonHistoryEntryId(), @@ -517,7 +735,7 @@ AdminLog::OwnedItem AboutView::makeNewPeerInfo(not_null user) { auto owned = AdminLog::OwnedItem(_delegate, item); owned->overrideMedia(std::make_unique( owned.get(), - GenerateNewPeerInfo(owned.get(), _item.get(), user), + GenerateNewPeerInfo(owned.get(), _item.get(), user, _commonGroups), HistoryView::MediaGenericDescriptor{ .service = true, .hideServiceText = true, diff --git a/Telegram/SourceFiles/history/view/history_view_about_view.h b/Telegram/SourceFiles/history/view/history_view_about_view.h index 2b304d92b..b52d7edf0 100644 --- a/Telegram/SourceFiles/history/view/history_view_about_view.h +++ b/Telegram/SourceFiles/history/view/history_view_about_view.h @@ -30,6 +30,9 @@ public: void make(Data::ChatIntro data, bool preview = false); + [[nodiscard]] rpl::producer<> refreshRequests() const; + [[nodiscard]] rpl::lifetime &lifetime(); + int top = 0; int height = 0; @@ -50,13 +53,22 @@ private: void setHelloChosen(not_null sticker); void toggleStickerRegistered(bool registered); + void loadCommonGroups(); + const not_null _history; const not_null _delegate; AdminLog::OwnedItem _item; + DocumentData *_helloChosen = nullptr; DocumentData *_sticker = nullptr; int _version = 0; + bool _commonGroupsStale = false; + bool _commonGroupsRequested = false; + std::vector> _commonGroups; + rpl::event_stream<> _refreshRequests; + rpl::lifetime _lifetime; + }; } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp index 287fbc9f1..abda42b78 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp @@ -511,6 +511,26 @@ void AttributeTable::draw( } } +TextState AttributeTable::textState( + QPoint point, + StateRequest request, + int outerWidth) const { + auto top = _margins.top(); + for (const auto &part : _parts) { + const auto height = st::normalFont->height + st::chatUniqueRowSkip; + if (point.y() >= top && point.y() < top + height) { + point -= QPoint((outerWidth - width()) / 2 + _valueLeft, top); + auto result = TextState(); + auto forText = request.forText(); + forText.align = style::al_topleft; + result.link = part.value.getState(point, width(), forText).link; + return result; + } + top += height; + } + return {}; +} + QSize AttributeTable::countOptimalSize() { auto maxLabel = 0; auto maxValue = 0; diff --git a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h index 3110a618e..4816b1f68 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h +++ b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h @@ -97,6 +97,10 @@ public: not_null owner, const PaintContext &context, int outerWidth) const override; + TextState textState( + QPoint point, + StateRequest request, + int outerWidth) const override; QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index b657a31ad..eba8783e0 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1237,3 +1237,10 @@ newPeerNonOfficial: IconEmoji { icon: icon{{ "chat/mini_info_alert", windowFg }}; padding: margins(0px, 2px, 0px, 0px); } +newPeerUserpics: GroupCallUserpics { + size: 16px; + shift: 5px; + stroke: 1px; + align: align(left); +} +newPeerUserpicsPadding: margins(0px, 3px, 0px, 0px); diff --git a/Telegram/SourceFiles/ui/unread_badge.cpp b/Telegram/SourceFiles/ui/unread_badge.cpp index 233ff51a9..d53f617da 100644 --- a/Telegram/SourceFiles/ui/unread_badge.cpp +++ b/Telegram/SourceFiles/ui/unread_badge.cpp @@ -296,9 +296,10 @@ void PeerBadge::set( _botVerifiedData = std::make_unique(); } if (details->iconId) { - _botVerifiedData->icon = factory( - Data::SerializeCustomEmojiId(details->iconId), - { .repaint = repaint }); + _botVerifiedData->icon = std::make_unique( + factory( + Data::SerializeCustomEmojiId(details->iconId), + { .repaint = repaint })); } } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index e51fe382b..b9ef54ad5 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit e51fe382bda9f2944412078da70f04de5dd821f3 +Subproject commit b9ef54ad5eb6932930244f30a00ac9169cd09a46 From 7c710e22cc17b9b8df818543332fee7287de47a0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Mar 2025 17:43:22 +0400 Subject: [PATCH 094/103] Always process mouse input in active window. Fixes #29008. --- Telegram/SourceFiles/history/history_inner_widget.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 362d5ef8b..c50dbe9e7 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -3896,7 +3896,8 @@ auto HistoryInner::reactionButtonParameters( } void HistoryInner::mouseActionUpdate() { - if (hasPendingResizedItems() || !_mouseActive) { + if (hasPendingResizedItems() + || (!_mouseActive && !window()->isActiveWindow())) { return; } From b6a31979f2c117852104f25e095f7c5d267787de Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Mar 2025 17:50:09 +0400 Subject: [PATCH 095/103] Add the same enter/leave check to ListWidget. --- .../SourceFiles/history/view/history_view_list_widget.cpp | 8 ++++++++ .../SourceFiles/history/view/history_view_list_widget.h | 1 + 2 files changed, 9 insertions(+) diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 10cea51b4..7a2d62cd9 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -2954,6 +2954,7 @@ void ListWidget::mousePressEvent(QMouseEvent *e) { e->accept(); return; // ignore mouse press, that was hiding context menu } + _mouseActive = true; mouseActionStart(e->globalPos(), e->button()); } @@ -3161,6 +3162,7 @@ void ListWidget::mouseMoveEvent(QMouseEvent *e) { mouseReleaseEvent(e); } if (reallyMoved) { + _mouseActive = true; lastGlobalPosition = e->globalPos(); if (!buttonsPressed || (_scrollDateLink @@ -3191,6 +3193,7 @@ rpl::producer ListWidget::touchMaybeSelectingValue() const { } void ListWidget::enterEventHook(QEnterEvent *e) { + _mouseActive = true; mouseActionUpdate(QCursor::pos()); return TWidget::enterEventHook(e); } @@ -3211,6 +3214,7 @@ void ListWidget::leaveEventHook(QEvent *e) { _cursor = style::cur_default; setCursor(_cursor); } + _mouseActive = false; return TWidget::leaveEventHook(e); } @@ -3643,6 +3647,10 @@ int ListWidget::SelectionViewOffset( void ListWidget::mouseActionUpdate() { + if (!_mouseActive && !window()->isActiveWindow()) { + return; + } + auto mousePosition = mapFromGlobal(_mousePosition); auto point = QPoint( std::clamp(mousePosition.x(), 0, width()), diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index a21cea06e..71db29cd5 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -816,6 +816,7 @@ private: uint16 _mouseTextSymbol = 0; bool _pressWasInactive = false; bool _overSenderUserpic = false; + bool _mouseActive = false; bool _selectEnabled = false; HistoryItem *_selectedTextItem = nullptr; From 72a35ba58b6f6060616229fd0e29fd7108684607 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Mar 2025 18:26:27 +0400 Subject: [PATCH 096/103] Show new-peer-info photo/name change. --- Telegram/SourceFiles/data/data_types.h | 2 + Telegram/SourceFiles/history/history.cpp | 96 +++++++++++++++++++ Telegram/SourceFiles/history/history.h | 4 + Telegram/SourceFiles/history/history_item.h | 3 + .../history/view/history_view_element.cpp | 3 + 5 files changed, 108 insertions(+) diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index cf9f6870a..9f0562e1b 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -349,6 +349,8 @@ enum class MessageFlag : uint64 { EstimatedDate = (1ULL << 49), ReactionsAllowed = (1ULL << 50), + + HideDisplayDate = (1ULL << 51), }; inline constexpr bool is_flag_type(MessageFlag) { return true; } using MessageFlags = base::flags; diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 129eed4de..1eb7d9107 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -137,6 +137,10 @@ void History::setHasPendingResizedItems() { void History::itemRemoved(not_null item) { if (item == _joinedMessage) { _joinedMessage = nullptr; + } else if (item == _newPeerNameChange) { + _newPeerNameChange = nullptr; + } else if (item == _newPeerPhotoChange) { + _newPeerPhotoChange = nullptr; } item->removeMainView(); if (_lastServerMessage == item) { @@ -1229,6 +1233,8 @@ void History::mainViewRemoved( not_null block, not_null view) { Expects(_joinedMessage != view->data()); + Expects(_newPeerNameChange != view->data()); + Expects(_newPeerPhotoChange != view->data()); if (_firstUnreadView == view) { getNextFirstUnreadMessage(); @@ -3243,6 +3249,85 @@ HistoryItem *History::insertJoinedMessage() { return _joinedMessage; } +void History::checkNewPeerMessages() { + if (!loadedAtTop()) { + return; + } + const auto user = peer->asUser(); + if (!user) { + return; + } + const auto photo = user->photoChangeDate(); + const auto name = user->nameChangeDate(); + if (!photo && _newPeerPhotoChange) { + _newPeerPhotoChange->destroy(); + } + if (!name && _newPeerNameChange) { + _newPeerNameChange->destroy(); + } + if ((!photo || _newPeerPhotoChange) && (!name || _newPeerNameChange)) { + return; + } + + const auto when = [](TimeId date) { + const auto now = base::unixtime::now(); + const auto passed = now - date; + if (passed < 3600) { + return tr::lng_new_contact_updated_now(tr::now); + } else if (passed < 24 * 3600) { + return tr::lng_new_contact_updated_hours( + tr::now, + lt_count, + (passed / 3600)); + } else if (passed < 60 * 24 * 3600) { + return tr::lng_new_contact_updated_days( + tr::now, + lt_count, + (passed / (24 * 3600))); + } + return tr::lng_new_contact_updated_months( + tr::now, + lt_count, + (passed / (30 * 24 * 3600))); + }; + + auto firstDate = TimeId(); + for (const auto &block : blocks) { + for (const auto &message : block->messages) { + const auto item = message->data(); + if (item != _newPeerPhotoChange && item != _newPeerNameChange) { + firstDate = item->date(); + break; + } + } + if (firstDate) { + break; + } + } + if (!firstDate) { + firstDate = base::unixtime::serialize( + QDateTime(QDate(2013, 8, 1), QTime(0, 0))); + } + const auto add = [&](tr::phrase phrase, TimeId date) { + const auto result = makeMessage({ + .id = owner().nextLocalMessageId(), + .flags = MessageFlag::Local | MessageFlag::HideDisplayDate, + .date = (--firstDate), + }, PreparedServiceText{ TextWithEntities{ + phrase(tr::now, lt_when, when(date)), + } }); + insertMessageToBlocks(result); + return result; + }; + + if (photo && !_newPeerPhotoChange) { + _newPeerPhotoChange = add(tr::lng_new_contact_updated_photo, photo); + } + if (name && !_newPeerNameChange) { + _newPeerNameChange = add(tr::lng_new_contact_updated_name, name); + } +} + void History::insertMessageToBlocks(not_null item) { Expects(item->mainView() == nullptr); @@ -3296,6 +3381,8 @@ void History::checkLocalMessages() { && peer->asChannel()->inviter && goodDate(peer->asChannel()->inviteDate)) { insertJoinedMessage(); + } else { + checkNewPeerMessages(); } } @@ -3309,6 +3396,15 @@ void History::removeJoinedMessage() { } } +void History::removeNewPeerMessages() { + if (_newPeerNameChange) { + _newPeerNameChange->destroy(); + } + if (_newPeerPhotoChange) { + _newPeerPhotoChange->destroy(); + } +} + void History::reactionsEnabledChanged(bool enabled) { if (!enabled) { for (const auto &item : _items) { diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index af7d86a4a..57b1203d0 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -82,6 +82,7 @@ public: [[nodiscard]] HistoryItem *joinedMessageInstance() const; void checkLocalMessages(); void removeJoinedMessage(); + void removeNewPeerMessages(); void reactionsEnabledChanged(bool enabled); @@ -545,6 +546,7 @@ private: HistoryItem *insertJoinedMessage(); void insertMessageToBlocks(not_null item); + void checkNewPeerMessages(); [[nodiscard]] Dialogs::BadgesState computeBadgesState() const; [[nodiscard]] Dialogs::BadgesState adjustBadgesStateByFolder( @@ -563,6 +565,8 @@ private: Element *_unreadBarView = nullptr; Element *_firstUnreadView = nullptr; HistoryItem *_joinedMessage = nullptr; + HistoryItem *_newPeerNameChange = nullptr; + HistoryItem *_newPeerPhotoChange = nullptr; bool _loadedAtTop = false; bool _loadedAtBottom = true; diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 8649400bc..06a115695 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -322,6 +322,9 @@ public: [[nodiscard]] bool hideEditedBadge() const { return (_flags & MessageFlag::HideEdited); } + [[nodiscard]] bool hideDisplayDate() const { + return (_flags & MessageFlag::HideDisplayDate); + } [[nodiscard]] bool isLocal() const { return _flags & MessageFlag::Local; } diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index cd8139c9e..e78b6c65d 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -1427,6 +1427,9 @@ bool Element::countIsTopicRootReply() const { void Element::setDisplayDate(bool displayDate) { const auto item = data(); + if (item->hideDisplayDate()) { + displayDate = false; + } if (displayDate && !Has()) { AddComponents(DateBadge::Bit()); Get()->init( From 4ff4e63a118fa116729f838e1e9db25fac15f90e Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Mar 2025 19:03:22 +0400 Subject: [PATCH 097/103] Fix sending paid intro sticker. --- .../history/history_inner_widget.cpp | 8 +++++++ .../history/history_inner_widget.h | 4 ++++ .../SourceFiles/history/history_widget.cpp | 6 ++++++ .../history/view/history_view_about_view.cpp | 21 ++++++++++++++----- .../history/view/history_view_about_view.h | 4 ++++ 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index c50dbe9e7..e470c651e 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -4346,6 +4346,9 @@ void HistoryInner::refreshAboutView(bool force) { _aboutView->refreshRequests() | rpl::start_with_next([=] { updateBotInfo(); }, _aboutView->lifetime()); + _aboutView->sendIntroSticker() | rpl::start_to_stream( + _sendIntroSticker, + _aboutView->lifetime()); } }; if (const auto user = _peer->asUser()) { @@ -4777,6 +4780,11 @@ ClickContext HistoryInner::prepareClickContext( }; } +auto HistoryInner::sendIntroSticker() const +-> rpl::producer> { + return _sendIntroSticker.events(); +} + auto HistoryInner::DelegateMixin() -> std::unique_ptr { return std::make_unique(); diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 6b4d350c5..1288f6e7b 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -221,6 +221,9 @@ public: Qt::MouseButton button, FullMsgId itemId) const; + [[nodiscard]] auto sendIntroSticker() const + -> rpl::producer>; + [[nodiscard]] static auto DelegateMixin() -> std::unique_ptr; @@ -466,6 +469,7 @@ private: std::unique_ptr _aboutView; std::unique_ptr _emptyPainter; std::unique_ptr _translateTracker; + rpl::event_stream> _sendIntroSticker; mutable History *_curHistory = nullptr; mutable int _curBlock = 0; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index ca5be0a39..dabb93a47 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -2525,6 +2525,12 @@ void HistoryWidget::showHistory( _scroll->hide(); _list = _scroll->setOwnedWidget( object_ptr(this, _scroll, controller(), _history)); + _list->sendIntroSticker( + ) | rpl::start_with_next([=](not_null sticker) { + sendExistingDocument( + sticker, + Api::MessageToSend(prepareSendAction({}))); + }, _list->lifetime()); _list->show(); if (const auto channel = _peer->asChannel()) { diff --git a/Telegram/SourceFiles/history/view/history_view_about_view.cpp b/Telegram/SourceFiles/history/view/history_view_about_view.cpp index c1f9346b5..d2ad1b735 100644 --- a/Telegram/SourceFiles/history/view/history_view_about_view.cpp +++ b/Telegram/SourceFiles/history/view/history_view_about_view.cpp @@ -199,7 +199,8 @@ auto GenerateChatIntro( not_null parent, Element *replacing, const Data::ChatIntro &data, - Fn)> helloChosen) + Fn)> helloChosen, + Fn)> sendIntroSticker) -> Fn, Fn)>)> { @@ -243,9 +244,7 @@ auto GenerateChatIntro( } } const auto send = [=] { - Api::SendExistingDocument(Api::MessageToSend( - Api::SendAction(parent->history()) - ), sticker); + sendIntroSticker(sticker); }; return StickerInBubblePart::Data{ .sticker = sticker, @@ -598,9 +597,17 @@ void AboutView::make(Data::ChatIntro data, bool preview) { } } }; + const auto sendIntroSticker = [=](not_null sticker) { + _sendIntroSticker.fire_copy(sticker); + }; owned->overrideMedia(std::make_unique( owned.get(), - GenerateChatIntro(owned.get(), _item.get(), data, helloChosen), + GenerateChatIntro( + owned.get(), + _item.get(), + data, + helloChosen, + sendIntroSticker), HistoryView::MediaGenericDescriptor{ .maxWidth = st::chatIntroWidth, .serviceLink = std::make_shared(handler), @@ -613,6 +620,10 @@ void AboutView::make(Data::ChatIntro data, bool preview) { setItem(std::move(owned), data.sticker); } +rpl::producer> AboutView::sendIntroSticker() const { + return _sendIntroSticker.events(); +} + rpl::producer<> AboutView::refreshRequests() const { return _refreshRequests.events(); } diff --git a/Telegram/SourceFiles/history/view/history_view_about_view.h b/Telegram/SourceFiles/history/view/history_view_about_view.h index b52d7edf0..b2c8d3cbb 100644 --- a/Telegram/SourceFiles/history/view/history_view_about_view.h +++ b/Telegram/SourceFiles/history/view/history_view_about_view.h @@ -30,6 +30,8 @@ public: void make(Data::ChatIntro data, bool preview = false); + [[nodiscard]] auto sendIntroSticker() const + -> rpl::producer>; [[nodiscard]] rpl::producer<> refreshRequests() const; [[nodiscard]] rpl::lifetime &lifetime(); @@ -63,6 +65,8 @@ private: DocumentData *_sticker = nullptr; int _version = 0; + rpl::event_stream> _sendIntroSticker; + bool _commonGroupsStale = false; bool _commonGroupsRequested = false; std::vector> _commonGroups; From 3bf7c44fc9bdd11d6cd097a59ae47e299902004e Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Mar 2025 20:50:29 +0400 Subject: [PATCH 098/103] Version 5.12. - Set a fee for incoming messages from unknown users. - Set a fee for messages in groups or channel comments. - Show some information about who's messaging you. - Pin gifts on your profile. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 4 ++-- Telegram/build/version | 10 +++++----- changelog.txt | 7 +++++++ 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index f6451e78d..2a77e1afa 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.12.0.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 79149edca..6deb2ee54 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,11,1,0 - PRODUCTVERSION 5,11,1,0 + FILEVERSION 5,12,0,0 + PRODUCTVERSION 5,12,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.11.1.0" + VALUE "FileVersion", "5.12.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.11.1.0" + VALUE "ProductVersion", "5.12.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index ad1416b33..677a3fb0e 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,11,1,0 - PRODUCTVERSION 5,11,1,0 + FILEVERSION 5,12,0,0 + PRODUCTVERSION 5,12,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "5.11.1.0" + VALUE "FileVersion", "5.12.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.11.1.0" + VALUE "ProductVersion", "5.12.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index fc748377f..39766ebfc 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs; constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs; constexpr auto AppName = "Telegram Desktop"_cs; constexpr auto AppFile = "Telegram"_cs; -constexpr auto AppVersion = 5011001; -constexpr auto AppVersionStr = "5.11.1"; +constexpr auto AppVersion = 5012000; +constexpr auto AppVersionStr = "5.12"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 2e287b997..4a130e9af 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5011001 -AppVersionStrMajor 5.11 -AppVersionStrSmall 5.11.1 -AppVersionStr 5.11.1 +AppVersion 5012000 +AppVersionStrMajor 5.12 +AppVersionStrSmall 5.12 +AppVersionStr 5.12.0 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 5.11.1 +AppVersionOriginal 5.12 diff --git a/changelog.txt b/changelog.txt index 97a490c23..765855446 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,10 @@ +5.12 (07.03.25) + +- Set a fee for incoming messages from unknown users. +- Set a fee for messages in groups or channel comments. +- Show some information about who's messaging you. +- Pin gifts on your profile. + 5.11.1 (13.02.25) - Fix arbitrary cropping support in the image editor. From b0d7c3e9b18885a8f1994aadadc6e7c91b27bd09 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Mar 2025 22:20:18 +0400 Subject: [PATCH 099/103] Revert "Re-enable ffmpeg optimizations on Linux" This reverts commit bd28ac6e1f3d3282d9edf8dbe8ec6f6397817032. It fails to link in Release mode with LTO. --- Telegram/build/docker/centos_env/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 16991cbef..48c5f3ccd 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -489,6 +489,8 @@ RUN git clone -b n6.1.1 --depth=1 {{ GIT }}/FFmpeg/FFmpeg.git \ --extra-cflags="-DCONFIG_SAFE_BITSTREAM_READER=1" \ --extra-cxxflags="-DCONFIG_SAFE_BITSTREAM_READER=1" \ --disable-debug \ + --disable-optimizations \ + --disable-inline-asm \ --disable-programs \ --disable-doc \ --disable-network \ From cc4a5f30b624ce04d8e4b3465974e690a2c070f2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Mar 2025 23:56:17 +0400 Subject: [PATCH 100/103] Fix a crash in some chat switchings. --- Telegram/SourceFiles/history/history_widget.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index dabb93a47..48cecb130 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -5194,7 +5194,9 @@ void HistoryWidget::updateSendButtonType() { : 0; }(); const auto perMessage = _peer ? _peer->starsPerMessageChecked() : 0; - const auto messages = _voiceRecordBar->isListenState() + const auto messages = !_peer + ? 0 + : _voiceRecordBar->isListenState() ? 1 : ComputeSendingMessagesCount(_history, { .forward = &_forwardPanel->items(), From 0537c5f2739eaa0aef9492f426bb977e68afa47b Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 8 Mar 2025 07:05:45 +0400 Subject: [PATCH 101/103] Fix crashes in empty repaint callbacks. --- .../view/media/history_view_sticker_player.cpp | 12 ++++++++++-- .../userpic/info_userpic_emoji_builder_preview.cpp | 2 +- .../SourceFiles/window/window_slide_animation.cpp | 8 ++++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker_player.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker_player.cpp index 64f6a883b..da9587057 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker_player.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker_player.cpp @@ -21,6 +21,10 @@ LottiePlayer::LottiePlayer(std::unique_ptr lottie) } void LottiePlayer::setRepaintCallback(Fn callback) { + if (!callback) { + _repaintLifetime.destroy(); + return; + } _repaintLifetime = _lottie->updates( ) | rpl::start_with_next([=](Lottie::Update update) { v::match(update.data, [&](const Lottie::Information &) { @@ -82,7 +86,9 @@ void WebmPlayer::clipCallback(ClipNotification notification) { case ClipNotification::Repaint: break; } - _repaintCallback(); + if (const auto onstack = _repaintCallback) { + onstack(); + } } void WebmPlayer::setRepaintCallback(Fn callback) { @@ -133,7 +139,9 @@ StaticStickerPlayer::StaticStickerPlayer( } void StaticStickerPlayer::setRepaintCallback(Fn callback) { - callback(); + if (callback) { + callback(); + } } bool StaticStickerPlayer::ready() { diff --git a/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_preview.cpp b/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_preview.cpp index 280b5c1b6..572809145 100644 --- a/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_preview.cpp +++ b/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_preview.cpp @@ -104,7 +104,7 @@ void PreviewPainter::setDocument( } if (_player) { _player->setRepaintCallback(updateCallback); - } else { + } else if (updateCallback) { updateCallback(); } }, _lifetime); diff --git a/Telegram/SourceFiles/window/window_slide_animation.cpp b/Telegram/SourceFiles/window/window_slide_animation.cpp index 54a843559..fced2ba81 100644 --- a/Telegram/SourceFiles/window/window_slide_animation.cpp +++ b/Telegram/SourceFiles/window/window_slide_animation.cpp @@ -194,11 +194,15 @@ void SlideAnimation::start() { fromLeft ? 0. : 1., st::slideDuration, transition()); - _repaintCallback(); + if (const auto onstack = _repaintCallback) { + onstack(); + } } void SlideAnimation::animationCallback() { - _repaintCallback(); + if (const auto onstack = _repaintCallback) { + onstack(); + } if (!_animation.animating()) { if (const auto onstack = _finishedCallback) { onstack(); From 6a3657ca87604c0e9b673bd590df18c28b3c74ce Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 8 Mar 2025 07:06:37 +0400 Subject: [PATCH 102/103] Version 5.12.1. - Fix a crash in some chat switchings. - Fix crashes in empty repaint callbacks. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 4 ++-- Telegram/build/version | 8 ++++---- changelog.txt | 5 +++++ 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 2a77e1afa..65aaca6cc 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.12.1.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 6deb2ee54..0371a5d90 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,12,0,0 - PRODUCTVERSION 5,12,0,0 + FILEVERSION 5,12,1,0 + PRODUCTVERSION 5,12,1,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.12.0.0" + VALUE "FileVersion", "5.12.1.0" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.12.0.0" + VALUE "ProductVersion", "5.12.1.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 677a3fb0e..d84add4dc 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,12,0,0 - PRODUCTVERSION 5,12,0,0 + FILEVERSION 5,12,1,0 + PRODUCTVERSION 5,12,1,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "5.12.0.0" + VALUE "FileVersion", "5.12.1.0" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.12.0.0" + VALUE "ProductVersion", "5.12.1.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 39766ebfc..08cc4d8ca 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs; constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs; constexpr auto AppName = "Telegram Desktop"_cs; constexpr auto AppFile = "Telegram"_cs; -constexpr auto AppVersion = 5012000; -constexpr auto AppVersionStr = "5.12"; +constexpr auto AppVersion = 5012001; +constexpr auto AppVersionStr = "5.12.1"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 4a130e9af..eac8b2553 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5012000 +AppVersion 5012001 AppVersionStrMajor 5.12 -AppVersionStrSmall 5.12 -AppVersionStr 5.12.0 +AppVersionStrSmall 5.12.1 +AppVersionStr 5.12.1 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 5.12 +AppVersionOriginal 5.12.1 diff --git a/changelog.txt b/changelog.txt index 765855446..9793a1e23 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +5.12.1 (08.03.25) + +- Fix a crash in some chat switchings. +- Fix crashes in empty repaint callbacks. + 5.12 (07.03.25) - Set a fee for incoming messages from unknown users. From fc67a801e3ca392971f157b3a2d4f9cb6648cc05 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 8 Mar 2025 07:09:15 +0400 Subject: [PATCH 103/103] Version 5.12.1: Fix build with Xcode. --- Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp index 63106347d..6bbde5e05 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp @@ -500,7 +500,7 @@ void InnerWidget::showMenuFor(not_null button, QPoint point) { break; } } - entry.pinnedSavedGifts = [pinnedIds, peer = _peer] { + entry.pinnedSavedGifts = [pinnedIds] { auto result = std::vector(); result.reserve(pinnedIds.size()); for (const auto &id : pinnedIds) {