diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 5cb49b2ab..0c8045d4d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4948,6 +4948,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_iv_join_channel" = "Join"; "lng_iv_window_title" = "Instant View"; +"lng_limit_download_title" = "Download speed limited"; +"lng_limit_download_subscribe" = "Subscribe to {link} and increase download speed {increase}."; +"lng_limit_download_increase_times#one" = "**{count}** time"; +"lng_limit_download_increase_times#other" = "**{count}** times"; +"lng_limit_download_increase_speed" = "by **{percent}**"; +"lng_limit_download_subscribe_link" = "Telegram Premium"; +"lng_limit_upload_title" = "Upload speed limited"; +"lng_limit_upload_subscribe" = "Subscribe to {link} and increase upload speed {increase}."; +"lng_limit_upload_increase_times#one" = "**{count}** time"; +"lng_limit_upload_increase_times#other" = "**{count}** times"; +"lng_limit_upload_increase_speed" = "by **{percent}**"; +"lng_limit_upload_subscribe_link" = "Telegram Premium"; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 0e1259c6a..c31d22440 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1679,6 +1679,20 @@ bool Session::queryItemVisibility(not_null item) const { return result; } +bool Session::queryDocumentVisibility( + not_null document) const { + const auto i = _documentItems.find(document); + if (i != end(_documentItems)) { + for (const auto &item : i->second) { + if (queryItemVisibility(item)) { + return true; + } + } + } + return false; +} + + [[nodiscard]] auto Session::itemVisibilityQueries() const -> rpl::producer { return _itemVisibilityQueries.events(); diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index b233aead1..0f7df77f4 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -271,6 +271,7 @@ public: not_null isVisible; }; [[nodiscard]] bool queryItemVisibility(not_null item) const; + [[nodiscard]] bool queryDocumentVisibility(not_null document) const; [[nodiscard]] rpl::producer itemVisibilityQueries() const; void itemVisibilitiesUpdated(); diff --git a/Telegram/SourceFiles/main/main_session_settings.cpp b/Telegram/SourceFiles/main/main_session_settings.cpp index cf451a20b..239bc7cf6 100644 --- a/Telegram/SourceFiles/main/main_session_settings.cpp +++ b/Telegram/SourceFiles/main/main_session_settings.cpp @@ -46,7 +46,8 @@ QByteArray SessionSettings::serialize() const { + sizeof(qint32) * 2 + _hiddenPinnedMessages.size() * (sizeof(quint64) * 3) + sizeof(qint32) - + _groupEmojiSectionHidden.size() * sizeof(quint64); + + _groupEmojiSectionHidden.size() * sizeof(quint64) + + sizeof(qint32) * 2; auto result = QByteArray(); result.reserve(size); @@ -98,6 +99,9 @@ QByteArray SessionSettings::serialize() const { for (const auto &peerId : _groupEmojiSectionHidden) { stream << SerializePeerId(peerId); } + stream + << qint32(_lastNonPremiumLimitDownload) + << qint32(_lastNonPremiumLimitUpload); } return result; } @@ -161,6 +165,8 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { qint32 photoEditorHintShowsCount = _photoEditorHintShowsCount; std::vector mutePeriods; qint32 legacySkipPremiumStickersSet = 0; + qint32 lastNonPremiumLimitDownload = 0; + qint32 lastNonPremiumLimitUpload = 0; stream >> versionTag; if (versionTag == kVersionTag) { @@ -434,6 +440,11 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { } } } + if (!stream.atEnd()) { + stream + >> lastNonPremiumLimitDownload + >> lastNonPremiumLimitUpload; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for SessionSettings::addFromSerialized()")); @@ -479,6 +490,8 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { _supportAllSilent = (supportAllSilent == 1); _photoEditorHintShowsCount = std::move(photoEditorHintShowsCount); _mutePeriods = std::move(mutePeriods); + _lastNonPremiumLimitDownload = lastNonPremiumLimitDownload; + _lastNonPremiumLimitUpload = lastNonPremiumLimitUpload; if (version < 2) { app.setLastSeenWarningSeen(appLastSeenWarningSeen == 1); diff --git a/Telegram/SourceFiles/main/main_session_settings.h b/Telegram/SourceFiles/main/main_session_settings.h index 222a94ead..c44b6f785 100644 --- a/Telegram/SourceFiles/main/main_session_settings.h +++ b/Telegram/SourceFiles/main/main_session_settings.h @@ -132,6 +132,19 @@ public: [[nodiscard]] std::vector mutePeriods() const; void addMutePeriod(TimeId period); + [[nodiscard]] TimeId lastNonPremiumLimitDownload() const { + return _lastNonPremiumLimitDownload; + } + [[nodiscard]] TimeId lastNonPremiumLimitUpload() const { + return _lastNonPremiumLimitUpload; + } + void setLastNonPremiumLimitDownload(TimeId when) { + _lastNonPremiumLimitDownload = when; + } + void setLastNonPremiumLimitUpload(TimeId when) { + _lastNonPremiumLimitUpload = when; + } + private: static constexpr auto kDefaultSupportChatsLimitSlice = 7 * 24 * 60 * 60; static constexpr auto kPhotoEditorHintMaxShowsCount = 5; @@ -158,6 +171,8 @@ private: bool _dialogsFiltersEnabled = false; int _photoEditorHintShowsCount = 0; std::vector _mutePeriods; + TimeId _lastNonPremiumLimitDownload = 0; + TimeId _lastNonPremiumLimitUpload = 0; Support::SwitchSettings _supportSwitch; bool _supportFixChatsOrder = true; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 0f1541ae8..d58315778 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -32,6 +32,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/dropdown_menu.h" #include "ui/focus_persister.h" #include "ui/resize_area.h" +#include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "window/window_connecting_widget.h" #include "window/window_top_bar_wrap.h" #include "window/notifications_manager.h" @@ -79,7 +81,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "export/view/export_view_panel_controller.h" #include "main/main_session.h" #include "main/main_session_settings.h" +#include "main/main_app_config.h" #include "main/main_account.h" +#include "settings/settings_premium.h" #include "support/support_helper.h" #include "storage/storage_user_photos.h" #include "styles/style_dialogs.h" @@ -1872,6 +1876,63 @@ bool MainWidget::preventsCloseSection( && preventsCloseSection(std::move(callback)); } +void MainWidget::showNonPremiumLimitToast(bool download) { + const auto parent = _mainSection + ? ((QWidget*)_mainSection.data()) + : (_dialogs && _history->isHidden()) + ? ((QWidget*)_dialogs.get()) + : ((QWidget*)_history.get()); + const auto link = download + ? tr::lng_limit_download_subscribe_link(tr::now) + : tr::lng_limit_upload_subscribe_link(tr::now); + const auto better = session().account().appConfig().get(download + ? u"upload_premium_speedup_download"_q + : u"upload_premium_speedup_upload"_q, 10.); + const auto percent = int(base::SafeRound(better * 100.)); + if (percent <= 100) { + return; + } + const auto increase = ((percent % 100) || percent <= 400) + ? (download + ? tr::lng_limit_download_increase_speed + : tr::lng_limit_upload_increase_speed)( + tr::now, + lt_percent, + TextWithEntities{ QString::number(percent - 100) }, + Ui::Text::RichLangValue) + : (download + ? tr::lng_limit_download_increase_times + : tr::lng_limit_upload_increase_times)( + tr::now, + lt_count, + percent / 100, + Ui::Text::RichLangValue); + auto text = (download + ? tr::lng_limit_download_subscribe + : tr::lng_limit_upload_subscribe)( + tr::now, + lt_link, + Ui::Text::Link(Ui::Text::Bold(link)), + lt_increase, + TextWithEntities{ increase }, + Ui::Text::RichLangValue); + auto filter = [=](ClickHandlerPtr handler, Qt::MouseButton button) { + Settings::ShowPremium( + controller(), + download ? u"download_limit"_q : u"upload_limit"_q); + return false; + }; + Ui::Toast::Show(parent, { + .title = (download + ? tr::lng_limit_download_title + : tr::lng_limit_upload_title)(tr::now), + .text = std::move(text), + .duration = 5 * crl::time(1000), + .slideSide = RectPart::Top, + .filter = std::move(filter), + }); +} + void MainWidget::showBackFromStack( const SectionShow ¶ms) { if (preventsCloseSection([=] { showBackFromStack(params); }, params)) { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index a2c396781..10bea63d4 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -233,6 +233,8 @@ public: Fn callback, const SectionShow ¶ms) const; + void showNonPremiumLimitToast(bool download); + void dialogsCancelled(); protected: diff --git a/Telegram/SourceFiles/storage/download_manager_mtproto.cpp b/Telegram/SourceFiles/storage/download_manager_mtproto.cpp index 77aae801b..b74eb7721 100644 --- a/Telegram/SourceFiles/storage/download_manager_mtproto.cpp +++ b/Telegram/SourceFiles/storage/download_manager_mtproto.cpp @@ -800,6 +800,10 @@ void DownloadMtprotoTask::getCdnFileHashesDone( void DownloadMtprotoTask::placeSentRequest( mtpRequestId requestId, const RequestData &requestData) { + if (_sentRequests.empty()) { + subscribeToNonPremiumLimit(); + } + const auto amount = _owner->changeRequestedAmount( dcId(), requestData.sessionIndex, @@ -815,6 +819,24 @@ void DownloadMtprotoTask::placeSentRequest( Ensures(ok1 && ok2); } +void DownloadMtprotoTask::subscribeToNonPremiumLimit() { + if (_nonPremiumLimitSubscription) { + return; + } + _owner->api().instance().nonPremiumDelayedRequests( + ) | rpl::start_with_next([=](mtpRequestId id) { + if (_sentRequests.contains(id)) { + if (const auto documentId = objectId()) { + const auto type = v::get( + _location.data).type(); + if (type == StorageFileLocation::Type::Document) { + _owner->notifyNonPremiumDelay(documentId); + } + } + } + }, _nonPremiumLimitSubscription); +} + auto DownloadMtprotoTask::finishSentRequest( mtpRequestId requestId, FinishRequestReason reason) @@ -833,6 +855,10 @@ auto DownloadMtprotoTask::finishSentRequest( _sentRequests.erase(it); const auto ok = _requestByOffset.remove(result.offset); + if (_sentRequests.empty()) { + _nonPremiumLimitSubscription.destroy(); + } + if (reason == FinishRequestReason::Success) { _owner->requestSucceeded( dcId(), diff --git a/Telegram/SourceFiles/storage/download_manager_mtproto.h b/Telegram/SourceFiles/storage/download_manager_mtproto.h index 6cc7421f6..a5916b491 100644 --- a/Telegram/SourceFiles/storage/download_manager_mtproto.h +++ b/Telegram/SourceFiles/storage/download_manager_mtproto.h @@ -57,6 +57,13 @@ public: void checkSendNextAfterSuccess(MTP::DcId dcId); [[nodiscard]] int chooseSessionIndex(MTP::DcId dcId) const; + void notifyNonPremiumDelay(DocumentId id) { + _nonPremiumDelays.fire_copy(id); + } + [[nodiscard]] rpl::producer nonPremiumDelays() const { + return _nonPremiumDelays.events(); + } + private: class Queue final { public: @@ -109,6 +116,7 @@ private: const not_null _api; rpl::event_stream<> _taskFinished; + rpl::event_stream _nonPremiumDelays; base::flat_map _balanceData; base::Timer _resetGenerationTimer; @@ -253,6 +261,8 @@ private: int64 offset, bytes::const_span buffer); + void subscribeToNonPremiumLimit(); + const not_null _owner; const MTP::DcId _dcId = 0; @@ -271,6 +281,8 @@ private: base::flat_map _cdnUncheckedParts; mtpRequestId _cdnHashesRequestId = 0; + rpl::lifetime _nonPremiumLimitSubscription; + }; } // namespace Storage diff --git a/Telegram/SourceFiles/storage/file_upload.cpp b/Telegram/SourceFiles/storage/file_upload.cpp index 72ebdaafe..c40396955 100644 --- a/Telegram/SourceFiles/storage/file_upload.cpp +++ b/Telegram/SourceFiles/storage/file_upload.cpp @@ -206,6 +206,13 @@ Uploader::Uploader(not_null api) ) | rpl::start_with_next([=](const FullMsgId &fullId) { processDocumentFailed(fullId); }, _lifetime); + + _api->instance().nonPremiumDelayedRequests( + ) | rpl::start_with_next([=](mtpRequestId id) { + if (dcMap.contains(id)) { + _nonPremiumDelayed.emplace(id); + } + }, _lifetime); } void Uploader::processPhotoProgress(const FullMsgId &newId) { @@ -359,7 +366,7 @@ void Uploader::upload( void Uploader::currentFailed() { auto j = queue.find(uploadingId); if (j != queue.end()) { - const auto &[msgId, file] = std::move(*j); + const auto [msgId, file] = std::move(*j); queue.erase(j); notifyFailed(msgId, file); } @@ -640,7 +647,7 @@ void Uploader::cancelAll() { currentFailed(); } while (!queue.empty()) { - const auto &[msgId, file] = std::move(*queue.begin()); + const auto [msgId, file] = std::move(*queue.begin()); queue.erase(queue.begin()); notifyFailed(msgId, file); } @@ -689,6 +696,7 @@ void Uploader::partLoaded(const MTPBool &result, mtpRequestId requestId) { if (i == requestsSent.cend()) { j = docRequestsSent.find(requestId); } + const auto wasNonPremiumDelayed = _nonPremiumDelayed.remove(requestId); if (i != requestsSent.cend() || j != docRequestsSent.cend()) { if (mtpIsFalse(result)) { // failed to upload current file currentFailed(); @@ -742,6 +750,9 @@ void Uploader::partLoaded(const MTPBool &result, mtpRequestId requestId) { file.fileSentSize, file.file->partssize }); } + if (wasNonPremiumDelayed) { + _nonPremiumDelays.fire_copy(fullId); + } } } @@ -750,6 +761,7 @@ void Uploader::partLoaded(const MTPBool &result, mtpRequestId requestId) { void Uploader::partFailed(const MTP::Error &error, mtpRequestId requestId) { // failed to upload current file + _nonPremiumDelayed.remove(requestId); if ((requestsSent.find(requestId) != requestsSent.cend()) || (docRequestsSent.find(requestId) != docRequestsSent.cend())) { currentFailed(); diff --git a/Telegram/SourceFiles/storage/file_upload.h b/Telegram/SourceFiles/storage/file_upload.h index de5a84a19..2e68307b9 100644 --- a/Telegram/SourceFiles/storage/file_upload.h +++ b/Telegram/SourceFiles/storage/file_upload.h @@ -98,6 +98,10 @@ public: return _secureFailed.events(); } + [[nodiscard]] rpl::producer nonPremiumDelays() const { + return _nonPremiumDelays.events(); + } + void unpause(); void sendNext(); void stopSessions(); @@ -126,6 +130,7 @@ private: base::flat_map requestsSent; base::flat_map docRequestsSent; base::flat_map dcMap; + base::flat_set _nonPremiumDelayed; uint32 sentSize = 0; // FileSize: Right now any file size fits 32 bit. uint32 sentSizes[MTP::kUploadSessionsCount] = { 0 }; @@ -143,6 +148,7 @@ private: rpl::event_stream _photoFailed; rpl::event_stream _documentFailed; rpl::event_stream _secureFailed; + rpl::event_stream _nonPremiumDelays; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 44868b6db..4e03d8765 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/player/media_player_instance.h" #include "media/view/media_view_open_common.h" #include "data/data_document_resolver.h" +#include "data/data_download_manager.h" #include "data/data_session.h" #include "data/data_file_origin.h" #include "data/data_folder.h" @@ -64,6 +65,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/confirm_box.h" #include "mainwidget.h" #include "main/main_account.h" +#include "main/main_app_config.h" #include "main/main_domain.h" #include "main/main_session.h" #include "main/main_session_settings.h" @@ -74,6 +76,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_blocked_peers.h" #include "support/support_helper.h" #include "storage/file_upload.h" +#include "storage/download_manager_mtproto.h" #include "window/themes/window_theme.h" #include "window/window_peer_menu.h" #include "window/window_session_controller_link_info.h" @@ -90,20 +93,6 @@ namespace { constexpr auto kCustomThemesInMemory = 5; constexpr auto kMaxChatEntryHistorySize = 50; -[[nodiscard]] Ui::ChatThemeBubblesData PrepareBubblesData( - const Data::CloudTheme &theme, - Data::CloudThemeType type) { - const auto i = theme.settings.find(type); - return { - .colors = (i != end(theme.settings) - ? i->second.outgoingMessagesColors - : std::vector()), - .accent = (i != end(theme.settings) - ? i->second.outgoingAccentColor - : std::optional()), - }; -} - class MainWindowShow final : public ChatHelpers::Show { public: explicit MainWindowShow(not_null controller); @@ -144,6 +133,29 @@ private: }; +[[nodiscard]] Ui::ChatThemeBubblesData PrepareBubblesData( + const Data::CloudTheme &theme, + Data::CloudThemeType type) { + const auto i = theme.settings.find(type); + return { + .colors = (i != end(theme.settings) + ? i->second.outgoingMessagesColors + : std::vector()), + .accent = (i != end(theme.settings) + ? i->second.outgoingAccentColor + : std::optional()), + }; +} + +[[nodiscard]] bool DownloadingDocument(not_null document) { + for (const auto id : Core::App().downloadManager().loadingList()) { + if (id->object.document == document.get()) { + return true; + } + } + return false; +} + MainWindowShow::MainWindowShow(not_null controller) : _window(base::make_weak(controller)) { } @@ -1192,6 +1204,16 @@ SessionController::SessionController( })); }, _lifetime); + session->downloader().nonPremiumDelays( + ) | rpl::start_with_next([=](DocumentId id) { + checkNonPremiumLimitToastDownload(id); + }, _lifetime); + + session->uploader().nonPremiumDelays( + ) | rpl::start_with_next([=](FullMsgId id) { + checkNonPremiumLimitToastUpload(id); + }, _lifetime); + session->addWindow(this); crl::on_main(this, [=] { @@ -1200,6 +1222,50 @@ SessionController::SessionController( }); } +bool SessionController::skipNonPremiumLimitToast(bool download) const { + if (session().premium()) { + return true; + } + const auto now = base::unixtime::now(); + const auto last = download + ? session().settings().lastNonPremiumLimitDownload() + : session().settings().lastNonPremiumLimitUpload(); + const auto delay = session().account().appConfig().get( + u"upload_premium_speedup_notify_period"_q, + 3600); + return (last && now < last + delay && now > last - delay); +} + +void SessionController::checkNonPremiumLimitToastDownload(DocumentId id) { + if (skipNonPremiumLimitToast(true)) { + return; + } + const auto document = session().data().document(id); + const auto visible = session().data().queryDocumentVisibility(document) + || DownloadingDocument(document); + if (!visible) { + return; + } + content()->showNonPremiumLimitToast(true); + const auto now = base::unixtime::now(); + session().settings().setLastNonPremiumLimitDownload(now); + session().saveSettingsDelayed(); +} + +void SessionController::checkNonPremiumLimitToastUpload(FullMsgId id) { + if (skipNonPremiumLimitToast(false)) { + return; + } else if (const auto item = session().data().message(id)) { + if (!session().data().queryItemVisibility(item)) { + return; + } + content()->showNonPremiumLimitToast(false); + const auto now = base::unixtime::now(); + session().settings().setLastNonPremiumLimitUpload(now); + session().saveSettingsDelayed(); + } +} + void SessionController::suggestArchiveAndMute() { const auto weak = base::make_weak(this); _window->show(Box([=](not_null box) { diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 4504f3b3c..063a2485a 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -650,6 +650,10 @@ private: CachedTheme &theme, bool generateGradient = true) const; + [[nodiscard]] bool skipNonPremiumLimitToast(bool download) const; + void checkNonPremiumLimitToastDownload(DocumentId id); + void checkNonPremiumLimitToastUpload(FullMsgId id); + const not_null _window; const std::unique_ptr _emojiInteractions; const bool _isPrimary = false;