Suggest premium on transfer speed limiting.

This commit is contained in:
John Preston 2024-03-28 13:47:06 +04:00
parent 0dd1a4973a
commit f65bc7c0bd
13 changed files with 262 additions and 17 deletions

View file

@ -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...";

View file

@ -1679,6 +1679,20 @@ bool Session::queryItemVisibility(not_null<HistoryItem*> item) const {
return result;
}
bool Session::queryDocumentVisibility(
not_null<DocumentData*> 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<Session::ItemVisibilityQuery> {
return _itemVisibilityQueries.events();

View file

@ -271,6 +271,7 @@ public:
not_null<bool*> isVisible;
};
[[nodiscard]] bool queryItemVisibility(not_null<HistoryItem*> item) const;
[[nodiscard]] bool queryDocumentVisibility(not_null<DocumentData*> document) const;
[[nodiscard]] rpl::producer<ItemVisibilityQuery> itemVisibilityQueries() const;
void itemVisibilitiesUpdated();

View file

@ -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<TimeId> 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);

View file

@ -132,6 +132,19 @@ public:
[[nodiscard]] std::vector<TimeId> 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<TimeId> _mutePeriods;
TimeId _lastNonPremiumLimitDownload = 0;
TimeId _lastNonPremiumLimitUpload = 0;
Support::SwitchSettings _supportSwitch;
bool _supportFixChatsOrder = true;

View file

@ -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<double>(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 &params) {
if (preventsCloseSection([=] { showBackFromStack(params); }, params)) {

View file

@ -233,6 +233,8 @@ public:
Fn<void()> callback,
const SectionShow &params) const;
void showNonPremiumLimitToast(bool download);
void dialogsCancelled();
protected:

View file

@ -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<StorageFileLocation>(
_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(),

View file

@ -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<DocumentId> nonPremiumDelays() const {
return _nonPremiumDelays.events();
}
private:
class Queue final {
public:
@ -109,6 +116,7 @@ private:
const not_null<ApiWrap*> _api;
rpl::event_stream<> _taskFinished;
rpl::event_stream<DocumentId> _nonPremiumDelays;
base::flat_map<MTP::DcId, DcBalanceData> _balanceData;
base::Timer _resetGenerationTimer;
@ -253,6 +261,8 @@ private:
int64 offset,
bytes::const_span buffer);
void subscribeToNonPremiumLimit();
const not_null<DownloadManagerMtproto*> _owner;
const MTP::DcId _dcId = 0;
@ -271,6 +281,8 @@ private:
base::flat_map<RequestData, QByteArray> _cdnUncheckedParts;
mtpRequestId _cdnHashesRequestId = 0;
rpl::lifetime _nonPremiumLimitSubscription;
};
} // namespace Storage

View file

@ -206,6 +206,13 @@ Uploader::Uploader(not_null<ApiWrap*> 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();

View file

@ -98,6 +98,10 @@ public:
return _secureFailed.events();
}
[[nodiscard]] rpl::producer<FullMsgId> nonPremiumDelays() const {
return _nonPremiumDelays.events();
}
void unpause();
void sendNext();
void stopSessions();
@ -126,6 +130,7 @@ private:
base::flat_map<mtpRequestId, QByteArray> requestsSent;
base::flat_map<mtpRequestId, int32> docRequestsSent;
base::flat_map<mtpRequestId, int32> dcMap;
base::flat_set<mtpRequestId> _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<FullMsgId> _photoFailed;
rpl::event_stream<FullMsgId> _documentFailed;
rpl::event_stream<FullMsgId> _secureFailed;
rpl::event_stream<FullMsgId> _nonPremiumDelays;
rpl::lifetime _lifetime;

View file

@ -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<QColor>()),
.accent = (i != end(theme.settings)
? i->second.outgoingAccentColor
: std::optional<QColor>()),
};
}
class MainWindowShow final : public ChatHelpers::Show {
public:
explicit MainWindowShow(not_null<SessionController*> 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<QColor>()),
.accent = (i != end(theme.settings)
? i->second.outgoingAccentColor
: std::optional<QColor>()),
};
}
[[nodiscard]] bool DownloadingDocument(not_null<DocumentData*> document) {
for (const auto id : Core::App().downloadManager().loadingList()) {
if (id->object.document == document.get()) {
return true;
}
}
return false;
}
MainWindowShow::MainWindowShow(not_null<SessionController*> 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<int>(
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<Ui::GenericBox*> box) {

View file

@ -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<Controller*> _window;
const std::unique_ptr<ChatHelpers::EmojiInteractions> _emojiInteractions;
const bool _isPrimary = false;