From 20f53c89adac85925bb95d19a13979f2c26f00e5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 1 Oct 2024 11:48:26 +0400 Subject: [PATCH 01/60] Fix poll bottom ripple. Fixes #28471. --- Telegram/SourceFiles/history/view/media/history_view_poll.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp index 6f924fc2e49c6..72b1df9ba4db3 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp @@ -851,7 +851,7 @@ void Poll::paintBottom( p.setOpacity(st::historyPollRippleOpacity); _linkRipple->paint( p, - left - st::msgPadding.left(), + left - st::msgPadding.left() - _linkRippleShift, height() - linkHeight, width(), &stm->msgWaveformInactive->c); From 677314dacba48b42e547c03636d673a4d7912e13 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 2 Oct 2024 11:20:27 +0400 Subject: [PATCH 02/60] Extract Data::MediaPreload. --- Telegram/CMakeLists.txt | 2 + .../SourceFiles/data/data_media_preload.cpp | 210 ++++++++++++++++++ .../SourceFiles/data/data_media_preload.h | 83 +++++++ Telegram/SourceFiles/data/data_story.cpp | 197 ++-------------- Telegram/SourceFiles/data/data_story.h | 13 +- 5 files changed, 317 insertions(+), 188 deletions(-) create mode 100644 Telegram/SourceFiles/data/data_media_preload.cpp create mode 100644 Telegram/SourceFiles/data/data_media_preload.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 83aadaeaacacc..abd2ef8f84fd9 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -569,6 +569,8 @@ PRIVATE data/data_lastseen_status.h data/data_location.cpp data/data_location.h + data/data_media_preload.cpp + data/data_media_preload.h data/data_media_rotation.cpp data/data_media_rotation.h data/data_media_types.cpp diff --git a/Telegram/SourceFiles/data/data_media_preload.cpp b/Telegram/SourceFiles/data/data_media_preload.cpp new file mode 100644 index 0000000000000..24b8ec401857d --- /dev/null +++ b/Telegram/SourceFiles/data/data_media_preload.cpp @@ -0,0 +1,210 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "data/data_media_preload.h" + +#include "data/data_document.h" +#include "data/data_document_media.h" +#include "data/data_file_origin.h" +#include "data/data_photo.h" +#include "data/data_photo_media.h" +#include "data/data_session.h" +#include "main/main_session.h" +#include "main/main_session_settings.h" +#include "media/streaming/media_streaming_reader.h" +#include "storage/file_download.h" // kMaxFileInMemory. + +namespace Data { +namespace { + +constexpr auto kDefaultPreloadPrefix = 4 * 1024 * 1024; + +[[nodiscard]] int64 ChoosePreloadPrefix(not_null video) { + const auto result = video->videoPreloadPrefix(); + return result + ? result + : std::min(int64(kDefaultPreloadPrefix), video->size); +} + +} // namespace + +MediaPreload::MediaPreload(Fn done) +: _done(std::move(done)) { +} + +void MediaPreload::callDone() { + if (const auto onstack = _done) { + onstack(); + } +} + +PhotoPreload::PhotoPreload( + not_null photo, + FileOrigin origin, + Fn done) +: MediaPreload(std::move(done)) +, _photo(photo->createMediaView()) { + start(origin); +} + +PhotoPreload::~PhotoPreload() { + if (_photo) { + base::take(_photo)->owner()->cancel(); + } +} + +bool PhotoPreload::Should( + not_null photo, + not_null context) { + return !photo->cancelled() + && AutoDownload::Should( + photo->session().settings().autoDownload(), + context, + photo); +} + +void PhotoPreload::start(FileOrigin origin) { + if (_photo->loaded()) { + callDone(); + } else { + _photo->owner()->load(origin, LoadFromCloudOrLocal, true); + _photo->owner()->session().downloaderTaskFinished( + ) | rpl::filter([=] { + return _photo->loaded(); + }) | rpl::start_with_next([=] { callDone(); }, _lifetime); + } +} + +VideoPreload::VideoPreload( + not_null video, + FileOrigin origin, + Fn done) +: MediaPreload(std::move(done)) +, DownloadMtprotoTask( + &video->session().downloader(), + video->videoPreloadLocation(), + origin) +, _video(video) +, _full(video->size) { + if (Can(video)) { + check(); + } else { + callDone(); + } +} + +void VideoPreload::check() { + const auto key = _video->bigFileBaseCacheKey(); + const auto weak = base::make_weak(static_cast(this)); + _video->owner().cacheBigFile().get(key, [weak]( + const QByteArray &result) { + if (!result.isEmpty()) { + crl::on_main([weak] { + if (const auto strong = weak.get()) { + static_cast(strong)->callDone(); + } + }); + } else { + crl::on_main([weak] { + if (const auto strong = weak.get()) { + static_cast(strong)->load(); + } + }); + } + }); +} + +void VideoPreload::load() { + if (!Can(_video)) { + callDone(); + return; + } + const auto key = _video->bigFileBaseCacheKey(); + const auto prefix = ChoosePreloadPrefix(_video); + Assert(prefix > 0 && prefix <= _video->size); + const auto part = Storage::kDownloadPartSize; + const auto parts = (prefix + part - 1) / part; + for (auto i = 0; i != parts; ++i) { + _parts.emplace(i * part, QByteArray()); + } + addToQueue(); +} + +void VideoPreload::done(QByteArray result) { + const auto key = _video->bigFileBaseCacheKey(); + if (!result.isEmpty() && key) { + Assert(result.size() < Storage::kMaxFileInMemory); + _video->owner().cacheBigFile().putIfEmpty( + key, + Storage::Cache::Database::TaggedValue(std::move(result), 0)); + } + callDone(); +} + +VideoPreload::~VideoPreload() { + if (!_finished && !_failed) { + cancelAllRequests(); + } +} + +bool VideoPreload::Can(not_null video) { + return video->canBeStreamed(nullptr) + && video->videoPreloadLocation().valid() + && video->bigFileBaseCacheKey(); +} + +bool VideoPreload::readyToRequest() const { + const auto part = Storage::kDownloadPartSize; + return !_failed && (_nextRequestOffset < _parts.size() * part); +} + +int64 VideoPreload::takeNextRequestOffset() { + Expects(readyToRequest()); + + _requestedOffsets.emplace(_nextRequestOffset); + _nextRequestOffset += Storage::kDownloadPartSize; + return _requestedOffsets.back(); +} + +bool VideoPreload::feedPart( + int64 offset, + const QByteArray &bytes) { + Expects(offset < _parts.size() * Storage::kDownloadPartSize); + Expects(_requestedOffsets.contains(int(offset))); + Expects(bytes.size() <= Storage::kDownloadPartSize); + + const auto part = Storage::kDownloadPartSize; + _requestedOffsets.remove(int(offset)); + _parts[offset] = bytes; + if ((_nextRequestOffset + part >= _parts.size() * part) + && _requestedOffsets.empty()) { + _finished = true; + removeFromQueue(); + auto result = ::Media::Streaming::SerializeComplexPartsMap(_parts); + if (result.size() == _full) { + // Make sure it is parsed as a complex map. + result.push_back(char(0)); + } + done(std::move(result)); + } + return true; +} + +void VideoPreload::cancelOnFail() { + _failed = true; + cancelAllRequests(); + done({}); +} + +bool VideoPreload::setWebFileSizeHook(int64 size) { + _failed = true; + cancelAllRequests(); + done({}); + return false; +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_media_preload.h b/Telegram/SourceFiles/data/data_media_preload.h new file mode 100644 index 0000000000000..30a47f4fad99c --- /dev/null +++ b/Telegram/SourceFiles/data/data_media_preload.h @@ -0,0 +1,83 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "storage/download_manager_mtproto.h" + +namespace Data { + +class PhotoMedia; +struct FileOrigin; + +class MediaPreload { +public: + explicit MediaPreload(Fn done); + virtual ~MediaPreload() = default; + +protected: + void callDone(); + +private: + Fn _done; + +}; + +class PhotoPreload final : public MediaPreload { +public: + [[nodiscard]] static bool Should( + not_null photo, + not_null context); + + PhotoPreload( + not_null data, + FileOrigin origin, + Fn done); + ~PhotoPreload(); + +private: + void start(FileOrigin origin); + + std::shared_ptr _photo; + rpl::lifetime _lifetime; + +}; + +class VideoPreload final + : public MediaPreload + , private Storage::DownloadMtprotoTask { +public: + [[nodiscard]] static bool Can(not_null video); + + VideoPreload( + not_null video, + FileOrigin origin, + Fn done); + ~VideoPreload(); + +private: + void check(); + void load(); + void done(QByteArray result); + + bool readyToRequest() const override; + int64 takeNextRequestOffset() override; + bool feedPart(int64 offset, const QByteArray &bytes) override; + void cancelOnFail() override; + bool setWebFileSizeHook(int64 size) override; + + const not_null _video; + base::flat_map _parts; + base::flat_set _requestedOffsets; + int64 _full = 0; + int _nextRequestOffset = 0; + bool _finished = false; + bool _failed = false; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index d9c37de4e10e6..9adcad0152ee2 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -13,6 +13,7 @@ For license and copyright information please follow this link: #include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_file_origin.h" +#include "data/data_media_preload.h" #include "data/data_photo.h" #include "data/data_photo_media.h" #include "data/data_user.h" @@ -230,107 +231,6 @@ using UpdateFlag = StoryUpdate::Flag; } // namespace -class StoryPreload::LoadTask final : private Storage::DownloadMtprotoTask { -public: - LoadTask( - FullStoryId id, - not_null document, - Fn done); - ~LoadTask(); - -private: - bool readyToRequest() const override; - int64 takeNextRequestOffset() override; - bool feedPart(int64 offset, const QByteArray &bytes) override; - void cancelOnFail() override; - bool setWebFileSizeHook(int64 size) override; - - base::flat_map _parts; - Fn _done; - base::flat_set _requestedOffsets; - int64 _full = 0; - int _nextRequestOffset = 0; - bool _finished = false; - bool _failed = false; - -}; - -StoryPreload::LoadTask::LoadTask( - FullStoryId id, - not_null document, - Fn done) -: DownloadMtprotoTask( - &document->session().downloader(), - document->videoPreloadLocation(), - id) -, _done(std::move(done)) -, _full(document->size) { - const auto prefix = document->videoPreloadPrefix(); - Assert(prefix > 0 && prefix <= document->size); - const auto part = Storage::kDownloadPartSize; - const auto parts = (prefix + part - 1) / part; - for (auto i = 0; i != parts; ++i) { - _parts.emplace(i * part, QByteArray()); - } - addToQueue(); -} - -StoryPreload::LoadTask::~LoadTask() { - if (!_finished && !_failed) { - cancelAllRequests(); - } -} - -bool StoryPreload::LoadTask::readyToRequest() const { - const auto part = Storage::kDownloadPartSize; - return !_failed && (_nextRequestOffset < _parts.size() * part); -} - -int64 StoryPreload::LoadTask::takeNextRequestOffset() { - Expects(readyToRequest()); - - _requestedOffsets.emplace(_nextRequestOffset); - _nextRequestOffset += Storage::kDownloadPartSize; - return _requestedOffsets.back(); -} - -bool StoryPreload::LoadTask::feedPart( - int64 offset, - const QByteArray &bytes) { - Expects(offset < _parts.size() * Storage::kDownloadPartSize); - Expects(_requestedOffsets.contains(int(offset))); - Expects(bytes.size() <= Storage::kDownloadPartSize); - - const auto part = Storage::kDownloadPartSize; - _requestedOffsets.remove(int(offset)); - _parts[offset] = bytes; - if ((_nextRequestOffset + part >= _parts.size() * part) - && _requestedOffsets.empty()) { - _finished = true; - removeFromQueue(); - auto result = ::Media::Streaming::SerializeComplexPartsMap(_parts); - if (result.size() == _full) { - // Make sure it is parsed as a complex map. - result.push_back(char(0)); - } - _done(result); - } - return true; -} - -void StoryPreload::LoadTask::cancelOnFail() { - _failed = true; - cancelAllRequests(); - _done({}); -} - -bool StoryPreload::LoadTask::setWebFileSizeHook(int64 size) { - _failed = true; - cancelAllRequests(); - _done({}); - return false; -} - Story::Story( StoryId id, not_null peer, @@ -999,95 +899,38 @@ PeerData *Story::fromPeer() const { } StoryPreload::StoryPreload(not_null story, Fn done) -: _story(story) -, _done(std::move(done)) { - start(); -} - -StoryPreload::~StoryPreload() { - if (_photo) { - base::take(_photo)->owner()->cancel(); - } -} - -FullStoryId StoryPreload::id() const { - return _story->fullId(); -} - -not_null StoryPreload::story() const { - return _story; -} - -void StoryPreload::start() { +: _story(story) { if (const auto photo = _story->photo()) { - _photo = photo->createMediaView(); - if (_photo->loaded()) { - callDone(); + if (PhotoPreload::Should(photo, story->peer())) { + _task = std::make_unique( + photo, + story->fullId(), + std::move(done)); } else { - _photo->automaticLoad(_story->fullId(), _story->peer()); - photo->session().downloaderTaskFinished( - ) | rpl::filter([=] { - return _photo->loaded(); - }) | rpl::start_with_next([=] { callDone(); }, _lifetime); + done(); } } else if (const auto video = _story->document()) { - if (video->canBeStreamed(nullptr) && video->videoPreloadPrefix()) { - const auto key = video->bigFileBaseCacheKey(); - if (key) { - const auto weak = base::make_weak(this); - video->owner().cacheBigFile().get(key, [weak]( - const QByteArray &result) { - if (!result.isEmpty()) { - crl::on_main([weak] { - if (const auto strong = weak.get()) { - strong->callDone(); - } - }); - } else { - crl::on_main([weak] { - if (const auto strong = weak.get()) { - strong->load(); - } - }); - } - }); - } else { - callDone(); - } + if (VideoPreload::Can(video)) { + _task = std::make_unique( + video, + story->fullId(), + std::move(done)); } else { - callDone(); + done(); } } else { - callDone(); + done(); } } -void StoryPreload::load() { - Expects(_story->document() != nullptr); +StoryPreload::~StoryPreload() = default; - const auto video = _story->document(); - const auto valid = video->videoPreloadLocation().valid(); - const auto prefix = video->videoPreloadPrefix(); - const auto key = video->bigFileBaseCacheKey(); - if (!valid || prefix <= 0 || prefix > video->size || !key) { - callDone(); - return; - } - _task = std::make_unique(id(), video, [=](QByteArray data) { - if (!data.isEmpty()) { - Assert(data.size() < Storage::kMaxFileInMemory); - _story->owner().cacheBigFile().putIfEmpty( - key, - Storage::Cache::Database::TaggedValue(std::move(data), 0)); - } - callDone(); - }); +FullStoryId StoryPreload::id() const { + return _story->fullId(); } -void StoryPreload::callDone() { - if (const auto onstack = _done) { - onstack(); - } +not_null StoryPreload::story() const { + return _story; } } // namespace Data diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index bd508591c94ad..490f979ce29f1 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -23,7 +23,7 @@ namespace Data { class Session; class Thread; -class PhotoMedia; +class MediaPreload; enum class StoryPrivacy : uchar { Public, @@ -301,18 +301,9 @@ class StoryPreload final : public base::has_weak_ptr { [[nodiscard]] not_null story() const; private: - class LoadTask; - - void start(); - void load(); - void callDone(); - const not_null _story; - Fn _done; - std::shared_ptr _photo; - std::unique_ptr _task; - rpl::lifetime _lifetime; + std::unique_ptr _task; }; From d3df2dc1e525db0a23385a07289dddb7c13357d1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 2 Oct 2024 11:45:33 +0400 Subject: [PATCH 03/60] Use Data::MediaPreload in sponsored messages. --- .../data/components/sponsored_messages.cpp | 95 ++++++++++--------- .../data/components/sponsored_messages.h | 6 +- 2 files changed, 53 insertions(+), 48 deletions(-) diff --git a/Telegram/SourceFiles/data/components/sponsored_messages.cpp b/Telegram/SourceFiles/data/components/sponsored_messages.cpp index 6a069979fc9ba..82a90e70076c1 100644 --- a/Telegram/SourceFiles/data/components/sponsored_messages.cpp +++ b/Telegram/SourceFiles/data/components/sponsored_messages.cpp @@ -12,10 +12,9 @@ For license and copyright information please follow this link: #include "core/click_handler_types.h" #include "data/data_channel.h" #include "data/data_document.h" -#include "data/data_document_media.h" #include "data/data_file_origin.h" +#include "data/data_media_preload.h" #include "data/data_photo.h" -#include "data/data_photo_media.h" #include "data/data_session.h" #include "data/data_user.h" #include "history/history.h" @@ -91,21 +90,7 @@ SponsoredMessages::AppendResult SponsoredMessages::append( if (entryIt == end(list.entries)) { list.showedAll = true; return SponsoredMessages::AppendResult::None; - } - if (const auto media = entryIt->documentMedia) { - const auto fullDuration = media->owner()->duration(); - if (fullDuration <= 0) { - if (!media->loaded()) { - return SponsoredMessages::AppendResult::MediaLoading; - } - } else { - constexpr auto kEnoughDuration = float64(2000); - if ((kEnoughDuration / fullDuration) > media->progress()) { - return SponsoredMessages::AppendResult::MediaLoading; - } - } - } - if (entryIt->photoMedia && !entryIt->photoMedia->loaded()) { + } else if (entryIt->preload) { return SponsoredMessages::AppendResult::MediaLoading; } entryIt->item.reset(history->addSponsoredMessage( @@ -284,15 +269,14 @@ void SponsoredMessages::append( const MTPSponsoredMessage &message) { const auto &data = message.data(); const auto randomId = data.vrandom_id().v; - auto mediaPhoto = std::shared_ptr(nullptr); - auto mediaDocument = std::shared_ptr(nullptr); + auto mediaPhoto = (PhotoData*)nullptr; + auto mediaDocument = (DocumentData*)nullptr; { if (data.vmedia()) { data.vmedia()->match([&](const MTPDmessageMediaPhoto &media) { if (const auto tlPhoto = media.vphoto()) { tlPhoto->match([&](const MTPDphoto &data) { - const auto p = history->owner().processPhoto(data); - mediaPhoto = p->createMediaView(); + mediaPhoto = history->owner().processPhoto(data); }, [](const MTPDphotoEmpty &) { }); } @@ -304,7 +288,7 @@ void SponsoredMessages::append( || d->isSilentVideo() || d->isAnimation() || d->isGifv()) { - mediaDocument = d->createMediaView(); + mediaDocument = d; } }, [](const MTPDdocumentEmpty &) { }); @@ -320,10 +304,8 @@ void SponsoredMessages::append( .photoId = data.vphoto() ? history->session().data().processPhoto(*data.vphoto())->id : PhotoId(0), - .mediaPhotoId = (mediaPhoto ? mediaPhoto->owner()->id : PhotoId(0)), - .mediaDocumentId = (mediaDocument - ? mediaDocument->owner()->id - : DocumentId(0)), + .mediaPhotoId = (mediaPhoto ? mediaPhoto->id : 0), + .mediaDocumentId = (mediaDocument ? mediaDocument->id : 0), .backgroundEmojiId = data.vcolor().has_value() ? data.vcolor()->data().vbackground_emoji_id().value_or_empty() : uint64(0), @@ -358,29 +340,54 @@ void SponsoredMessages::append( .additionalInfo = std::move(additionalInfo), }; list.entries.push_back({ - nullptr, - {}, - std::move(sharedMessage), - mediaPhoto, - mediaDocument, + .sponsored = std::move(sharedMessage), }); - - const auto fileOrigin = FullMsgId( + auto &entry = list.entries.back(); + const auto itemId = entry.itemFullId = FullMsgId( history->peer->id, _session->data().nextLocalMessageId()); - list.entries.back().itemFullId = fileOrigin; + const auto fileOrigin = FileOrigin(); // No way to refresh in ads. + + static const auto kFlaggedPreload = ((MediaPreload*)nullptr) + 1; + const auto preloaded = [=] { + const auto i = _data.find(history); + if (i == end(_data)) { + return; + } + auto &entries = i->second.entries; + const auto j = ranges::find(entries, itemId, &Entry::itemFullId); + if (j == end(entries)) { + return; + } + auto &entry = *j; + if (entry.preload.get() == kFlaggedPreload) { + entry.preload.release(); + } else { + entry.preload = nullptr; + } + }; + + auto preload = std::unique_ptr(); + entry.preload.reset(kFlaggedPreload); if (mediaPhoto) { - mediaPhoto->owner()->load( - list.entries.back().itemFullId, - LoadFromCloudOrLocal, - true); - } - if (mediaDocument) { - mediaDocument->owner()->save( + preload = std::make_unique( + mediaPhoto, + fileOrigin, + preloaded); + } else if (mediaDocument && VideoPreload::Can(mediaDocument)) { + preload = std::make_unique( + mediaDocument, fileOrigin, - QString(), - LoadFromCloudOrLocal, - true); + preloaded); + } + // Preload constructor may have called preloaded(), which zero-ed + // entry.preload, that way we're ready and don't need to save it. + // Otherwise we're preloading and need to save the task. + if (entry.preload.get() == kFlaggedPreload) { + entry.preload.release(); + if (preload) { + entry.preload = std::move(preload); + } } } diff --git a/Telegram/SourceFiles/data/components/sponsored_messages.h b/Telegram/SourceFiles/data/components/sponsored_messages.h index 32cc08e5d8cc0..028861af9ac53 100644 --- a/Telegram/SourceFiles/data/components/sponsored_messages.h +++ b/Telegram/SourceFiles/data/components/sponsored_messages.h @@ -20,8 +20,7 @@ class Session; namespace Data { -class DocumentMedia; -class PhotoMedia; +class MediaPreload; struct SponsoredReportResult final { using Id = QByteArray; @@ -122,8 +121,7 @@ class SponsoredMessages final { OwnedItem item; FullMsgId itemFullId; SponsoredMessage sponsored; - std::shared_ptr photoMedia; - std::shared_ptr documentMedia; + std::unique_ptr preload; }; struct List { std::vector entries; From 8d93eb919b037481500d31ddc2475cd9ef753a33 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 2 Oct 2024 21:50:30 +0300 Subject: [PATCH 04/60] Added verified badge to title in panel for web view bots. --- .../SourceFiles/inline_bots/bot_attach_web_view.cpp | 13 +++++++++++++ .../ui/chat/attach/attach_bot_webview.cpp | 3 +++ .../SourceFiles/ui/chat/attach/attach_bot_webview.h | 2 ++ Telegram/lib_ui | 2 +- 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 9213f761b92e6..8d9512c05dc66 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -66,6 +66,7 @@ For license and copyright information please follow this link: #include "styles/style_boxes.h" #include "styles/style_channel_earn.h" #include "styles/style_chat.h" +#include "styles/style_info.h" // infoVerifiedCheck. #include "styles/style_layers.h" #include "styles/style_menu_icons.h" #include "styles/style_window.h" @@ -1057,6 +1058,17 @@ void WebViewInstance::maybeChooseAndRequestButton(PeerTypes supported) { void WebViewInstance::show(const QString &url, uint64 queryId) { auto title = Info::Profile::NameValue(_bot); + auto titleBadge = _bot->isVerified() + ? object_ptr(_parentShow->toastParent()) + : nullptr; + if (titleBadge) { + const auto raw = titleBadge.data(); + raw->paintRequest() | rpl::start_with_next([=] { + auto p = Painter(raw); + st::infoVerifiedCheck.paint(p, st::lineWidth, 0, raw->width()); + }, raw->lifetime()); + raw->resize(st::infoVerifiedCheck.size() + QSize(0, st::lineWidth)); + } const auto &bots = _session->attachWebView().attachBots(); @@ -1087,6 +1099,7 @@ void WebViewInstance::show(const QString &url, uint64 queryId) { .url = url, .storageId = _session->local().resolveStorageIdBots(), .title = std::move(title), + .titleBadge = std::move(titleBadge), .bottom = rpl::single('@' + _bot->username()), .delegate = static_cast(this), .menuButtons = buttons, diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 0fed7b2297744..1cb0d40232af5 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -366,6 +366,7 @@ Panel::Progress::Progress(QWidget *parent, Fn rect) Panel::Panel( const Webview::StorageId &storageId, rpl::producer title, + object_ptr titleBadge, not_null delegate, MenuButtons menuButtons, bool allowClipboardRead) @@ -412,6 +413,7 @@ Panel::Panel( }, _widget->lifetime()); setTitle(std::move(title)); + _widget->setTitleBadge(std::move(titleBadge)); } Panel::~Panel() { @@ -1620,6 +1622,7 @@ std::unique_ptr Show(Args &&args) { auto result = std::make_unique( args.storageId, std::move(args.title), + std::move(args.titleBadge), args.delegate, args.menuButtons, args.allowClipboardRead); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h index 7a5901430df02..f03dcdc345696 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h @@ -76,6 +76,7 @@ class Panel final : public base::has_weak_ptr { Panel( const Webview::StorageId &storageId, rpl::producer title, + object_ptr titleBadge, not_null delegate, MenuButtons menuButtons, bool allowClipboardRead); @@ -191,6 +192,7 @@ struct Args { QString url; Webview::StorageId storageId; rpl::producer title; + object_ptr titleBadge = { nullptr }; rpl::producer bottom; not_null delegate; MenuButtons menuButtons; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index a53c0a747ff49..3b4db4b98c134 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit a53c0a747ff495e0985bbb1647272c53cb29d501 +Subproject commit 3b4db4b98c1341e19cbc7b584a0243b7d6a51510 From 440ebfcbf68417910c97bdce1a1e51562309969c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 2 Oct 2024 22:07:20 +0300 Subject: [PATCH 05/60] Removed display of userpic for sponsored messages. --- Telegram/SourceFiles/history/view/history_view_message.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index b8c22a68d7698..f3582b2864295 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -2367,7 +2367,9 @@ bool Message::hasFromPhoto() const { case Context::SavedSublist: case Context::ScheduledTopic: { const auto item = data(); - if (item->isPostHidingAuthor()) { + if (item->isSponsored()) { + return false; + } else if (item->isPostHidingAuthor()) { return false; } else if (item->isPost()) { return true; From b60c7e97ab184ec09bca358efa27ad85bd432b75 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 3 Oct 2024 11:16:06 +0400 Subject: [PATCH 06/60] Don't show full query in "nothing found" info. --- Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 6e60f072dfc73..ab6577613e22c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -87,6 +87,7 @@ namespace { constexpr auto kHashtagResultsLimit = 5; constexpr auto kStartReorderThreshold = 30; +constexpr auto kQueryPreviewLimit = 32; [[nodiscard]] int FixedOnTopDialogsCount(not_null list) { auto result = 0; @@ -145,11 +146,14 @@ constexpr auto kStartReorderThreshold = 30; tr::now, Ui::Text::Bold)); if (!trimmed.isEmpty()) { + const auto preview = (trimmed.size() > kQueryPreviewLimit + 3) + ? (trimmed.mid(0, kQueryPreviewLimit) + Ui::kQEllipsis) + : trimmed; text.append("\n").append( tr::lng_search_tab_no_results_text( tr::now, lt_query, - trimmed)); + trimmed.mid(0, kQueryPreviewLimit))); if (hashtag) { text.append("\n").append( tr::lng_search_tab_no_results_retry(tr::now)); From 7d67b3d00a0620b0a26375c0eeb45a6ee7dbd506 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 3 Oct 2024 11:16:22 +0400 Subject: [PATCH 07/60] Use circle userpics in reactions from forums. --- Telegram/SourceFiles/data/data_peer.cpp | 5 +++-- Telegram/SourceFiles/data/data_peer.h | 14 +++++++++++--- .../history/view/history_view_group_call_bar.cpp | 9 +++++---- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index bf3a7c84cf20f..35e43b158507b 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -394,7 +394,8 @@ void PeerData::paintUserpic( Ui::PeerUserpicView &view, int x, int y, - int size) const { + int size, + bool forceCircle) const { const auto cloud = userpicCloudImage(view); const auto ratio = style::DevicePixelRatio(); Ui::ValidateUserpicCache( @@ -402,7 +403,7 @@ void PeerData::paintUserpic( cloud, cloud ? nullptr : ensureEmptyUserpic().get(), size * ratio, - isForum()); + !forceCircle && isForum()); p.drawImage(QRect(x, y, size, size), view.cached); } diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index d252b0c01e890..5433ced7f5ceb 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -317,15 +317,23 @@ class PeerData { Ui::PeerUserpicView &view, int x, int y, - int size) const; + int size, + bool forceCircle = false) const; void paintUserpicLeft( Painter &p, Ui::PeerUserpicView &view, int x, int y, int w, - int size) const { - paintUserpic(p, view, rtl() ? (w - x - size) : x, y, size); + int size, + bool forceCircle = false) const { + paintUserpic( + p, + view, + rtl() ? (w - x - size) : x, + y, + size, + forceCircle); } void loadUserpic(); [[nodiscard]] bool hasUserpic() const; diff --git a/Telegram/SourceFiles/history/view/history_view_group_call_bar.cpp b/Telegram/SourceFiles/history/view/history_view_group_call_bar.cpp index 1406b7e5ba1fd..c460ab1e01fb1 100644 --- a/Telegram/SourceFiles/history/view/history_view_group_call_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_group_call_bar.cpp @@ -38,13 +38,14 @@ void GenerateUserpicsInRow( const auto single = st.size; const auto shift = st.shift; const auto width = single + (limit - 1) * (single - shift); - if (result.width() != width * style::DevicePixelRatio()) { + const auto ratio = style::DevicePixelRatio(); + if (result.width() != width * ratio) { result = QImage( - QSize(width, single) * style::DevicePixelRatio(), + QSize(width, single) * ratio, QImage::Format_ARGB32_Premultiplied); } result.fill(Qt::transparent); - result.setDevicePixelRatio(style::DevicePixelRatio()); + result.setDevicePixelRatio(ratio); auto q = Painter(&result); auto hq = PainterHighQualityEnabler(q); @@ -54,7 +55,7 @@ void GenerateUserpicsInRow( for (auto i = count; i != 0;) { auto &entry = list[--i]; q.setCompositionMode(QPainter::CompositionMode_SourceOver); - entry.peer->paintUserpic(q, entry.view, x, 0, single); + entry.peer->paintUserpic(q, entry.view, x, 0, single, true); entry.uniqueKey = entry.peer->userpicUniqueKey(entry.view); q.setCompositionMode(QPainter::CompositionMode_Source); q.setBrush(Qt::NoBrush); From a8b0f2934b8e30a54ebff1b07eabbe3f3a6f8731 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 3 Oct 2024 11:53:35 +0400 Subject: [PATCH 08/60] Scroll quote selection in the draft options box. --- .../controls/history_view_draft_options.cpp | 33 +++++++++++++++++-- .../history/view/history_view_message.cpp | 23 +++++++++---- 2 files changed, 46 insertions(+), 10 deletions(-) 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 e93ba510664e6..d0c8ca2b8230f 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -110,6 +110,10 @@ class PreviewWrap final : public Ui::RpWidget { const std::vector &links, const QString &usedLink); + [[nodiscard]] rpl::producer draggingScrollDelta() const { + return _draggingScrollDelta.events(); + } + private: void paintEvent(QPaintEvent *e) override; void leaveEventHook(QEvent *e) override; @@ -118,6 +122,11 @@ class PreviewWrap final : public Ui::RpWidget { void mouseReleaseEvent(QMouseEvent *e) override; void mouseDoubleClickEvent(QMouseEvent *e) override; + void visibleTopBottomUpdated(int top, int bottom) override { + _visibleTop = top; + _visibleBottom = bottom; + } + void initElement(); void highlightUsedLink( const TextWithTags &message, @@ -141,6 +150,9 @@ class PreviewWrap final : public Ui::RpWidget { rpl::lifetime _elementLifetime; QPoint _position; + rpl::event_stream _draggingScrollDelta; + int _visibleTop = 0; + int _visibleBottom = 0; base::Timer _trippleClickTimer; ClickHandlerPtr _link; @@ -423,9 +435,8 @@ void PreviewWrap::mouseMoveEvent(QMouseEvent *e) { : Flag::LookupLink), .onlyMessageText = (_section == Section::Link || _onlyMessageText), }; - auto resolved = _element->textState( - e->pos() - _position, - request); + const auto position = e->pos(); + auto resolved = _element->textState(position - _position, request); _over = true; const auto text = (_section == Section::Reply) && (resolved.cursor == CursorState::Text); @@ -450,6 +461,17 @@ void PreviewWrap::mouseMoveEvent(QMouseEvent *e) { update(); } } + + _draggingScrollDelta.fire([&] { + if (!_selecting || _visibleTop >= _visibleBottom) { + return 0; + } else if (position.y() < _visibleTop) { + return position.y() - _visibleTop; + } else if (position.y() >= _visibleBottom) { + return position.y() + 1 - _visibleBottom; + } + return 0; + }()); } void PreviewWrap::mousePressEvent(QMouseEvent *e) { @@ -814,6 +836,11 @@ void DraftOptionsBox( state->wrap = box->addRow( object_ptr(box, args.history), {}); + state->wrap->draggingScrollDelta( + ) | rpl::start_with_next([=](int delta) { + box->scrollByDraggingDelta(delta); + }, state->wrap->lifetime()); + const auto &linkRanges = args.links; state->shown.value() | rpl::start_with_next([=](Section shown) { bottom->clear(); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index f3582b2864295..9f8c3111fcae1 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -2400,8 +2400,10 @@ TextState Message::textState( const auto media = this->media(); auto result = TextState(item); + const auto visibleMediaTextLen = visibleMediaTextLength(); + const auto visibleTextLen = visibleTextLength(); const auto minSymbol = (_invertMedia && request.onlyMessageText) - ? visibleMediaTextLength() + ? visibleMediaTextLen : 0; result.symbol = minSymbol; @@ -2428,6 +2430,7 @@ TextState Message::textState( g.setHeight(g.height() - reactionsHeight); const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip); if (_reactions->getState(point - reactionsPosition, &result)) { + result.symbol += visibleMediaTextLen + visibleTextLen; return result; } } @@ -2443,6 +2446,7 @@ TextState Message::textState( auto inner = g; if (getStateCommentsButton(point, inner, &result)) { + result.symbol += visibleMediaTextLen + visibleTextLen; return result; } auto trect = inner.marginsRemoved(st::msgPadding); @@ -2460,6 +2464,7 @@ TextState Message::textState( trect.setHeight(trect.height() - reactionsHeight); const auto reactionsPosition = QPoint(trect.left(), trect.top() + trect.height() + reactionsTop); if (_reactions->getState(point - reactionsPosition, &result)) { + result.symbol += visibleMediaTextLen + visibleTextLen; return result; } } @@ -2475,6 +2480,7 @@ TextState Message::textState( ? inner : inner - heightMargins), &result)) { + result.symbol += visibleMediaTextLen + visibleTextLen; return result; } if (belowInfo) { @@ -2552,7 +2558,11 @@ TextState Message::textState( result = bottomInfoResult; } }; - if (result.symbol <= minSymbol && inBubble) { + if (!inBubble) { + if (point.y() >= g.y() + g.height()) { + result.symbol += visibleTextLen + visibleMediaTextLen; + } + } else if (result.symbol <= minSymbol) { const auto mediaHeight = mediaDisplayed ? media->height() : 0; const auto mediaLeft = trect.x() - st::msgPadding.left(); const auto mediaTop = (!mediaDisplayed || _invertMedia) @@ -2575,22 +2585,21 @@ TextState Message::textState( result.cursor = CursorState::None; } } else if (request.onlyMessageText) { - result.symbol = visibleTextLength(); + result.symbol = visibleTextLen; result.afterSymbol = false; result.cursor = CursorState::None; } else { - result.symbol += visibleTextLength(); + result.symbol += visibleTextLen; } } else if (getStateText(point, trect, &result, request)) { if (_invertMedia) { - result.symbol += visibleMediaTextLength(); + result.symbol += visibleMediaTextLen; } result.overMessageText = true; checkBottomInfoState(); return result; } else if (point.y() >= trect.y() + trect.height()) { - result.symbol = visibleTextLength() - + visibleMediaTextLength(); + result.symbol = visibleTextLen + visibleMediaTextLen; } } checkBottomInfoState(); From 15fcb73e19159321e7371a6882ebfcfaa89a207d Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 3 Oct 2024 11:59:57 +0400 Subject: [PATCH 09/60] Beta version 5.5.8. - Allow drag-to-scroll in reply quote edit box. - Add bot verified badge to web app title. - Fix crash when opening some channels. - Fix some visual glitches. --- 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 | 7 +++++++ 6 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 0be56dc7a6c21..dc7f37dc2dede 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.5.8.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index a79db0fa55ad7..fc8aa589b9813 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,5,7,0 - PRODUCTVERSION 5,5,7,0 + FILEVERSION 5,5,8,0 + PRODUCTVERSION 5,5,8,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.5.7.0" + VALUE "FileVersion", "5.5.8.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.5.7.0" + VALUE "ProductVersion", "5.5.8.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index fb2a4d60c5172..47b389f63f58b 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,5,7,0 - PRODUCTVERSION 5,5,7,0 + FILEVERSION 5,5,8,0 + PRODUCTVERSION 5,5,8,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.5.7.0" + VALUE "FileVersion", "5.5.8.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.5.7.0" + VALUE "ProductVersion", "5.5.8.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index c636ce0efb83d..e0f9527d06bc8 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 = 5005007; -constexpr auto AppVersionStr = "5.5.7"; +constexpr auto AppVersion = 5005008; +constexpr auto AppVersionStr = "5.5.8"; constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index ceec60ace12ce..6568587094f10 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5005007 +AppVersion 5005008 AppVersionStrMajor 5.5 -AppVersionStrSmall 5.5.7 -AppVersionStr 5.5.7 +AppVersionStrSmall 5.5.8 +AppVersionStr 5.5.8 BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 5.5.7.beta +AppVersionOriginal 5.5.8.beta diff --git a/changelog.txt b/changelog.txt index f84965d5fcd7b..24cbdc1577754 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,10 @@ +5.5.8 beta (03.10.24) + +- Allow drag-to-scroll in reply quote edit box. +- Add bot verified badge to web app title. +- Fix crash when opening some channels. +- Fix some visual glitches. + 5.5.7 beta (01.10.24) - Add ability to share QR code of bots. From 335095a332607c41a8d20b47e61f5bbd66366d4b Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 3 Oct 2024 12:13:33 +0400 Subject: [PATCH 10/60] Beta version 5.5.8: Fix build with Xcode. --- Telegram/SourceFiles/data/components/sponsored_messages.cpp | 2 +- Telegram/SourceFiles/data/data_media_preload.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/data/components/sponsored_messages.cpp b/Telegram/SourceFiles/data/components/sponsored_messages.cpp index 82a90e70076c1..f4ec3a3565faf 100644 --- a/Telegram/SourceFiles/data/components/sponsored_messages.cpp +++ b/Telegram/SourceFiles/data/components/sponsored_messages.cpp @@ -348,7 +348,7 @@ void SponsoredMessages::append( _session->data().nextLocalMessageId()); const auto fileOrigin = FileOrigin(); // No way to refresh in ads. - static const auto kFlaggedPreload = ((MediaPreload*)nullptr) + 1; + static const auto kFlaggedPreload = ((MediaPreload*)quintptr(0x01)); const auto preloaded = [=] { const auto i = _data.find(history); if (i == end(_data)) { diff --git a/Telegram/SourceFiles/data/data_media_preload.cpp b/Telegram/SourceFiles/data/data_media_preload.cpp index 24b8ec401857d..8fb938067362d 100644 --- a/Telegram/SourceFiles/data/data_media_preload.cpp +++ b/Telegram/SourceFiles/data/data_media_preload.cpp @@ -123,7 +123,6 @@ void VideoPreload::load() { callDone(); return; } - const auto key = _video->bigFileBaseCacheKey(); const auto prefix = ChoosePreloadPrefix(_video); Assert(prefix > 0 && prefix <= _video->size); const auto part = Storage::kDownloadPartSize; From e3bc4dab8507ce6c5494322b2f2a527bb54c4999 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 16 Sep 2024 11:23:16 +0400 Subject: [PATCH 11/60] Update API scheme to layer 189. --- Telegram/SourceFiles/api/api_chat_invite.cpp | 1 - .../SourceFiles/boxes/send_credits_box.cpp | 7 +- .../export/data/export_data_types.cpp | 13 ++++ .../export/data/export_data_types.h | 77 +++++++++++-------- .../export/output/export_output_html.cpp | 5 ++ .../export/output/export_output_json.cpp | 8 ++ Telegram/SourceFiles/history/history_item.cpp | 13 ++++ Telegram/SourceFiles/mtproto/scheme/api.tl | 22 +++++- .../SourceFiles/payments/payments_form.cpp | 2 + 9 files changed, 108 insertions(+), 40 deletions(-) diff --git a/Telegram/SourceFiles/api/api_chat_invite.cpp b/Telegram/SourceFiles/api/api_chat_invite.cpp index dde941b33e965..be07e72ac1d4f 100644 --- a/Telegram/SourceFiles/api/api_chat_invite.cpp +++ b/Telegram/SourceFiles/api/api_chat_invite.cpp @@ -275,7 +275,6 @@ void ConfirmSubscriptionBox( : 0; state->api->request( MTPpayments_SendStarsForm( - MTP_flags(0), MTP_long(formId), MTP_inputInvoiceChatInviteSubscription(MTP_string(hash))) ).done([=](const MTPpayments_PaymentResult &result) { diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index efa5d46cfea58..1bfeb01cc594a 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -272,10 +272,13 @@ void SendCreditsBox( state->confirmButtonBusy = true; session->api().request( MTPpayments_SendStarsForm( - MTP_flags(0), MTP_long(form->formId), form->inputInvoice) - ).done([=](auto result) { + ).done([=](const MTPpayments_PaymentResult &result) { + result.match([&](const MTPDpayments_paymentResult &data) { + session->api().applyUpdates(data.vupdates()); + }, [](const MTPDpayments_paymentVerificationNeeded &data) { + }); if (weak) { state->confirmButtonBusy = false; box->closeBox(); diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 4eb403263838a..7bd2d35159937 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1658,6 +1658,19 @@ ServiceAction ParseServiceAction( .giveawayMsgId = data.vgiveaway_msg_id().v, .isUnclaimed = data.is_unclaimed(), }; + }, [&](const MTPDmessageActionStarGift &data) { + const auto &gift = data.vgift().data(); + result.content = ActionStarGift{ + .giftId = uint64(gift.vid().v), + .stars = int64(gift.vstars().v), + .text = (data.vmessage() + ? ParseText( + data.vmessage()->data().vtext(), + data.vmessage()->data().ventities().v) + : std::vector()), + .anonymous = data.is_name_hidden(), + .limited = gift.is_limited(), + }; }, [](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 d07b4de559fad..e075549740cda 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -401,6 +401,39 @@ Media ParseMedia( const QString &folder, TimeId date); +struct TextPart { + enum class Type { + Text, + Unknown, + Mention, + Hashtag, + BotCommand, + Url, + Email, + Bold, + Italic, + Code, + Pre, + TextUrl, + MentionName, + Phone, + Cashtag, + Underline, + Strike, + Blockquote, + BankCard, + Spoiler, + CustomEmoji, + }; + Type type = Type::Text; + Utf8String text; + Utf8String additional; + + [[nodiscard]] static Utf8String UnavailableEmoji() { + return "(unavailable)"; + } +}; + struct ActionChatCreate { Utf8String title; std::vector userIds; @@ -617,6 +650,14 @@ struct ActionPrizeStars { bool isUnclaimed = false; }; +struct ActionStarGift { + uint64 giftId = 0; + int64 stars = 0; + std::vector text; + bool anonymous = false; + bool limited = false; +}; + struct ServiceAction { std::variant< v::null_t, @@ -661,7 +702,8 @@ struct ServiceAction { ActionBoostApply, ActionPaymentRefunded, ActionGiftStars, - ActionPrizeStars> content; + ActionPrizeStars, + ActionStarGift> content; }; ServiceAction ParseServiceAction( @@ -669,39 +711,6 @@ ServiceAction ParseServiceAction( const MTPMessageAction &data, const QString &mediaFolder); -struct TextPart { - enum class Type { - Text, - Unknown, - Mention, - Hashtag, - BotCommand, - Url, - Email, - Bold, - Italic, - Code, - Pre, - TextUrl, - MentionName, - Phone, - Cashtag, - Underline, - Strike, - Blockquote, - BankCard, - Spoiler, - CustomEmoji, - }; - Type type = Type::Text; - Utf8String text; - Utf8String additional; - - [[nodiscard]] static Utf8String UnavailableEmoji() { - return "(unavailable)"; - } -}; - struct Reaction { enum class Type { Empty, diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index 21e6b0893e661..61f122b8d234c 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -1362,6 +1362,11 @@ auto HtmlWriter::Wrap::pushMessage( + ".\n Your prize is " + QString::number(data.amount).toUtf8() + " Telegram Stars."; + }, [&](const ActionStarGift &data) { + return serviceFrom + + " sent you a gift of " + + QByteArray::number(data.stars) + + " 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 3dba1a90e1221..a91c07dbdf648 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -647,6 +647,14 @@ QByteArray SerializeMessage( push("is_unclaimed", data.isUnclaimed); push("giveaway_msg_id", data.giveawayMsgId); push("transaction_id", data.transactionId); + }, [&](const ActionStarGift &data) { + pushActor(); + pushAction("send_star_gift"); + push("gift_id", data.giftId); + push("stars", data.stars); + push("is_limited", data.limited); + push("is_anonymous", data.anonymous); + pushBare("text", SerializeText(context, data.text)); }, [](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 ea92626c446fa..67166e01d0f68 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -5270,6 +5270,12 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return result; }; + auto prepareStarGift = [&]( + const MTPDmessageActionStarGift &action) { + auto result = PreparedServiceText(); + return result; + }; + setServiceText(action.match( prepareChatAddUserText, prepareChatJoinedByLink, @@ -5315,6 +5321,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { preparePaymentRefunded, prepareGiftStars, prepareGiftPrize, + prepareStarGift, PrepareEmptyText, PrepareErrorText)); @@ -5427,6 +5434,12 @@ void HistoryItem::applyAction(const MTPMessageAction &action) { .viaGiveaway = true, .unclaimed = data.is_unclaimed(), }); + }, [&](const MTPDmessageActionStarGift &data) { + _media = std::make_unique( + this, + _from, + Data::GiftType::Credits, + data.vstars_amount().v); }, [](const auto &) { }); } diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 39865d48e1c54..261788c0803e1 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -183,6 +183,7 @@ messageActionRequestedPeerSentMe#93b31848 button_id:int peers: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 = UserFull; +userFull#1f58e369 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 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 = UserFull; contact#145ade0b user_id:long mutual:Bool = Contact; @@ -915,6 +916,7 @@ upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mti payments.paymentForm#a0058751 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:long title:string description:string photo:flags.5?WebDocument invoice:Invoice provider_id:long url:string native_provider:flags.4?string native_params:flags.4?DataJSON additional_methods:flags.6?Vector saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?Vector users:Vector = payments.PaymentForm; payments.paymentFormStars#7bf6b15c flags:# form_id:long bot_id:long title:string description:string photo:flags.5?WebDocument invoice:Invoice users:Vector = payments.PaymentForm; +payments.paymentFormStarGift#b425cfe1 form_id:long invoice:Invoice = payments.PaymentForm; payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector = payments.ValidatedRequestedInfo; @@ -1464,6 +1466,7 @@ inputInvoiceSlug#c326caef slug:string = InputInvoice; inputInvoicePremiumGiftCode#98986c0d purpose:InputStorePaymentPurpose option:PremiumGiftCodeOption = InputInvoice; inputInvoiceStars#65f00ce3 purpose:InputStorePaymentPurpose = InputInvoice; inputInvoiceChatInviteSubscription#34e793f1 hash:string = InputInvoice; +inputInvoiceStarGift#25d8c1d8 flags:# hide_name:flags.0?true user_id:InputUser gift_id:long message:flags.1?TextWithEntities = InputInvoice; payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice; @@ -1859,6 +1862,15 @@ starsGiveawayOption#94ce852a flags:# extended:flags.0?true default:flags.1?true starsGiveawayWinnersOption#54236209 flags:# default:flags.0?true users:int per_user_stars:long = StarsGiveawayWinnersOption; +starGift#aea174ee flags:# limited:flags.0?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int convert_stars:long = StarGift; + +payments.starGiftsNotModified#a388a368 = payments.StarGifts; +payments.starGifts#901689ea hash:int gifts:Vector = payments.StarGifts; + +userStarGift#eea49a6e flags:# name_hidden:flags.0?true unsaved:flags.5?true from_id:flags.1?long date:int gift:StarGift message:flags.2?TextWithEntities msg_id:flags.3?int convert_stars:flags.4?long = UserStarGift; + +payments.userStarGifts#6b65b517 flags:# count:int gifts:Vector next_offset:flags.0?string users:Vector = payments.UserStarGifts; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -2413,7 +2425,7 @@ payments.launchPrepaidGiveaway#5ff58f20 peer:InputPeer giveaway_id:long purpose: payments.getStarsTopupOptions#c00ec7d3 = Vector; payments.getStarsStatus#104fcfa7 peer:InputPeer = payments.StarsStatus; payments.getStarsTransactions#69da4557 flags:# inbound:flags.0?true outbound:flags.1?true ascending:flags.2?true subscription_id:flags.3?string peer:InputPeer offset:string limit:int = payments.StarsStatus; -payments.sendStarsForm#2bb731d flags:# form_id:long invoice:InputInvoice = payments.PaymentResult; +payments.sendStarsForm#7998c914 form_id:long invoice:InputInvoice = payments.PaymentResult; payments.refundStarsCharge#25ae8f4a user_id:InputUser charge_id:string = Updates; payments.getStarsRevenueStats#d91ffad6 flags:# dark:flags.0?true peer:InputPeer = payments.StarsRevenueStats; payments.getStarsRevenueWithdrawalUrl#13bbe8b3 peer:InputPeer stars:long password:InputCheckPasswordSRP = payments.StarsRevenueWithdrawalUrl; @@ -2424,6 +2436,10 @@ payments.getStarsSubscriptions#32512c5 flags:# missing_balance:flags.0?true peer payments.changeStarsSubscription#c7770878 flags:# peer:InputPeer subscription_id:string canceled:flags.0?Bool = Bool; payments.fulfillStarsSubscription#cc5bebb3 peer:InputPeer subscription_id:string = Bool; payments.getStarsGiveawayOptions#bd1efd3e = Vector; +payments.getStarGifts#c4563590 hash:int = payments.StarGifts; +payments.getUserStarGifts#5e72c7e1 user_id:InputUser offset:string limit:int = payments.UserStarGifts; +payments.saveStarGift#87acf08e flags:# unsave:flags.0?true user_id:InputUser msg_id:int = Bool; +payments.convertStarGift#421e027 user_id:InputUser msg_id:int = 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; @@ -2543,4 +2559,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo; -// LAYER 188 +// LAYER 189 diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 472de9d9cf533..bddfb53218035 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -460,6 +460,8 @@ void Form::requestForm() { .inputInvoice = inputInvoice(), }; _updates.fire(CreditsPaymentStarted{ .data = formData }); + }, [&](const MTPDpayments_paymentFormStarGift &data) { + // todo pay for star gift. }); }).fail([=](const MTP::Error &error) { hideProgress(); From 761617c1ce18fbf04b5f61bc0f88240bbea6235c Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 6 Sep 2024 14:56:23 +0400 Subject: [PATCH 12/60] Start UI of the new gift sending box. --- Telegram/Resources/langs/lang.strings | 11 + Telegram/SourceFiles/api/api_premium.cpp | 18 + Telegram/SourceFiles/api/api_premium.h | 9 +- .../SourceFiles/boxes/gift_credits_box.cpp | 566 ++++++++++++++++-- .../SourceFiles/settings/settings_main.cpp | 4 +- Telegram/SourceFiles/ui/effects/credits.style | 22 + 6 files changed, 567 insertions(+), 63 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0da7bbf6213fa..4b6d36107f369 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2981,6 +2981,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_stars_incoming" = "Use Stars to unlock content and services on Telegram."; "lng_gift_until" = "Until"; +"lng_gift_premium_subtitle" = "Gift Premium"; +"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_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 >"; +"lng_gift_stars_limited" = "limited"; +"lng_gift_stars_tabs_all" = "All Gifts"; +"lng_gift_stars_tabs_limited" = "Limited"; + "lng_accounts_limit_title" = "Limit Reached"; "lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account."; "lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts."; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 0cd7a308b8eb2..7c2934652aa54 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -550,6 +550,24 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice( }; } +std::vector PremiumGiftCodeOptions::optionsForPeer() const { + auto result = std::vector(); + + if (!_optionsForOnePerson.currency.isEmpty()) { + const auto count = int(_optionsForOnePerson.months.size()); + result.reserve(count); + for (auto i = 0; i != count; ++i) { + Assert(i < _optionsForOnePerson.totalCosts.size()); + result.push_back({ + .cost = _optionsForOnePerson.totalCosts[i], + .currency = _optionsForOnePerson.currency, + .months = _optionsForOnePerson.months[i], + }); + } + } + return result; +} + Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) { const auto it = _subscriptionOptions.find(amount); if (it != end(_subscriptionOptions)) { diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index d78811ceb1584..d778b00d40de1 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -67,6 +67,12 @@ struct GiveawayInfo { } }; +struct GiftOptionData { + int64 cost = 0; + QString currency; + int months = 0; +}; + class Premium final { public: explicit Premium(not_null api); @@ -171,6 +177,7 @@ class PremiumGiftCodeOptions final { PremiumGiftCodeOptions(not_null peer); [[nodiscard]] rpl::producer request(); + [[nodiscard]] std::vector optionsForPeer() const; [[nodiscard]] Data::PremiumSubscriptionOptions options(int amount); [[nodiscard]] const std::vector &availablePresets() const; [[nodiscard]] int monthsFromPreset(int monthsIndex); @@ -206,7 +213,7 @@ class PremiumGiftCodeOptions final { base::flat_map _subscriptionOptions; struct { std::vector months; - std::vector totalCosts; + std::vector totalCosts; QString currency; } _optionsForOnePerson; diff --git a/Telegram/SourceFiles/boxes/gift_credits_box.cpp b/Telegram/SourceFiles/boxes/gift_credits_box.cpp index f0ecd05f63a0b..77b44f6e0dcef 100644 --- a/Telegram/SourceFiles/boxes/gift_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_credits_box.cpp @@ -8,6 +8,7 @@ For license and copyright information please follow this link: #include "boxes/gift_credits_box.h" #include "api/api_credits.h" +#include "api/api_premium.h" #include "boxes/peer_list_controllers.h" #include "data/data_peer.h" #include "data/data_session.h" @@ -15,11 +16,16 @@ For license and copyright information please follow this link: #include "data/stickers/data_custom_emoji.h" #include "lang/lang_keys.h" #include "main/session/session_show.h" +#include "main/main_session.h" #include "settings/settings_credits_graphics.h" +#include "settings/settings_credits.h" +#include "settings/settings_premium.h" #include "ui/controls/userpic_button.h" #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_stars_colored.h" +#include "ui/effects/ripple_animation.h" #include "ui/layers/generic_box.h" +#include "ui/painter.h" #include "ui/rect.h" #include "ui/text/text_utilities.h" #include "ui/vertical_list.h" @@ -33,39 +39,485 @@ For license and copyright information please follow this link: #include "styles/style_layers.h" #include "styles/style_premium.h" +#include "data/stickers/data_stickers.h" +#include "data/data_document.h" + namespace Ui { +namespace { + +constexpr auto kPriceTabAll = 0; +constexpr auto kPriceTabLimited = -1; + +struct GiftTypePremium { + int64 cost = 0; + QString currency; + int months = 0; + int discountPercent = 0; + + [[nodiscard]] friend inline bool operator==( + const GiftTypePremium &, + const GiftTypePremium &) = default; +}; + +struct GiftTypeStars { + DocumentData *document = nullptr; + int stars = 0; + bool limited = false; + + [[nodiscard]] friend inline bool operator==( + const GiftTypeStars&, + const GiftTypeStars&) = default; +}; + +struct GiftDescriptor : std::variant { + using variant::variant; + + [[nodiscard]] friend inline bool operator==( + const GiftDescriptor&, + const GiftDescriptor&) = default; +}; + +[[nodiscard]] rpl::producer> GiftsPremium( + not_null session, + not_null peer) { + struct Session { + std::vector last; + }; + static auto Map = base::flat_map, Session>(); + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + + auto i = Map.find(session); + if (i == end(Map)) { + i = Map.emplace(session, Session()).first; + session->lifetime().add([=] { Map.remove(session); }); + } + if (!i->second.last.empty()) { + consumer.put_next_copy(i->second.last); + } + + using namespace Api; + const auto api = lifetime.make_state(peer); + api->request() | rpl::start_with_error_done([=](QString error) { + consumer.put_next({}); + }, [=] { + const auto &options = api->optionsForPeer(); + auto list = std::vector(); + 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(); + } + } + for (auto &gift : list) { + if (gift.months > minMonthsGift.months + && gift.currency == minMonthsGift.currency) { + const auto costPerMonth = gift.cost / gift.months; + const auto maxCostPerMonth = minMonthsGift.cost + / minMonthsGift.months; + const auto costRatio = costPerMonth / maxCostPerMonth; + const auto discount = 1. - costRatio; + const auto discountPercent = 100 * discount; + const auto value = int(base::SafeRound(discountPercent)); + if (value > 0 && value < 100) { + gift.discountPercent = value; + } + } + } + Map[session].last = list; + consumer.put_next_copy(list); + }, lifetime); + + return lifetime; + }; +} + +[[nodiscard]] rpl::producer> GiftsStars( + not_null session, + not_null peer) { + //struct Session { + // std::vector last; + //}; + //static auto Map = base::flat_map, Session>(); + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + + auto list = std::vector(); + + const auto add = [&](uint64 setId, int price, bool limited) { + auto &sets = session->data().stickers().setsRef(); + const auto i = sets.find(setId); + if (i != end(sets)) { + for (const auto document : i->second->stickers) { + price = document->isPremiumSticker() ? 1000 : price; + list.push_back({ + .document = document, + .stars = price, + .limited = limited, + }); + } + } + }; + add(Data::Stickers::CloudRecentSetId, 100, false); + add(Data::Stickers::RecentSetId, 250, false); + add(Data::Stickers::FavedSetId, 50, true); + + consumer.put_next(std::move(list)); + + return lifetime; + }; +} -void GiftCreditsBox( - not_null box, +[[nodiscard]] Text::String TabTextForPrice( + not_null session, + int price) { + const auto simple = [](const QString &text) { + return Text::String(st::semiboldTextStyle, text); + }; + if (price == kPriceTabAll) { + return simple(tr::lng_gift_stars_tabs_all(tr::now)); + } else if (price == kPriceTabLimited) { + return simple(tr::lng_gift_stars_tabs_limited(tr::now)); + } + 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); + return result; +} + +struct GiftPriceTabs { + rpl::producer priceTab; + object_ptr widget; +}; +[[nodiscard]] GiftPriceTabs MakeGiftsPriceTabs( + not_null window, + not_null peer, + rpl::producer> gifts) { + auto widget = object_ptr((QWidget*)nullptr); + const auto raw = widget.data(); + + struct Button { + QRect geometry; + Text::String text; + int price = 0; + bool active = false; + }; + struct State { + rpl::variable> prices; + rpl::variable priceTab = kPriceTabAll; + std::vector