From 2673e1df5368ba9f5988ca2857bad5fbfee7d68c Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 5 Jun 2024 18:29:16 +0400 Subject: [PATCH 001/142] Fix IV window on Linux --- Telegram/SourceFiles/iv/iv_controller.cpp | 25 +++++++++++------------ 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index cc9d727fe..3b30dc6db 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -221,7 +221,7 @@ Controller::~Controller() { void Controller::updateTitleGeometry(int newWidth) const { _subtitleWrap->setGeometry( 0, - st::windowTitleHeight, + 0, newWidth, st::ivSubtitleHeight); _subtitleWrap->paintRequest() | rpl::start_with_next([=](QRect clip) { @@ -241,7 +241,7 @@ void Controller::updateTitleGeometry(int newWidth) const { } void Controller::initControls() { - _subtitleWrap = std::make_unique(_window.get()); + _subtitleWrap = std::make_unique(_window->body().get()); _subtitleText = _index.value() | rpl::filter( rpl::mappers::_1 >= 0 ) | rpl::map([=](int index) { @@ -275,7 +275,7 @@ void Controller::initControls() { _back->toggledValue( ) | rpl::start_with_next([=](bool toggled) { _subtitleLeft.start( - [=] { updateTitleGeometry(_window->width()); }, + [=] { updateTitleGeometry(_window->body()->width()); }, toggled ? 0. : 1., toggled ? 1. : 0., st::fadeWrapDuration); @@ -353,7 +353,7 @@ void Controller::createWindow() { initControls(); - window->widthValue() | rpl::start_with_next([=](int width) { + window->body()->widthValue() | rpl::start_with_next([=](int width) { updateTitleGeometry(width); }, _subtitle->lifetime()); @@ -366,13 +366,13 @@ void Controller::createWindow() { _delegate->ivSaveGeometry(window); }, window->lifetime()); - _container = Ui::CreateChild(window->window()); + _container = Ui::CreateChild(window->body().get()); rpl::combine( - window->sizeValue(), + window->body()->sizeValue(), _subtitleWrap->heightValue() ) | rpl::start_with_next([=](QSize size, int title) { _container->setGeometry(QRect(QPoint(), size).marginsRemoved( - { 0, title + st::windowTitleHeight, 0, 0 })); + { 0, title, 0, 0 })); }, _container->lifetime()); _container->paintRequest() | rpl::start_with_next([=](QRect clip) { @@ -807,11 +807,10 @@ void Controller::showShareMenu() { } _shareWrap = std::make_unique(_shareHidesContent - ? _window->window() + ? _window->body().get() : nullptr); - const auto margins = QMargins(0, st::windowTitleHeight, 0, 0); if (!_shareHidesContent) { - _shareWrap->setGeometry(_window->geometry().marginsRemoved(margins)); + _shareWrap->setGeometry(_window->body()->rect()); _shareWrap->setWindowFlag(Qt::FramelessWindowHint); _shareWrap->setAttribute(Qt::WA_TranslucentBackground); _shareWrap->setAttribute(Qt::WA_NoSystemBackground); @@ -819,14 +818,14 @@ void Controller::showShareMenu() { _shareContainer.reset(QWidget::createWindowContainer( _shareWrap->windowHandle(), - _window.get(), + _window->body().get(), Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint)); } - _window->sizeValue() | rpl::start_with_next([=](QSize size) { + _window->body()->sizeValue() | rpl::start_with_next([=](QSize size) { const auto widget = _shareHidesContent ? _shareWrap.get() : _shareContainer.get(); - widget->setGeometry(QRect(QPoint(), size).marginsRemoved(margins)); + widget->setGeometry(QRect(QPoint(), size)); }, _shareWrap->lifetime()); auto result = _showShareBox({ From e4d88f829c3fbc397f5a9255169ab023b12ff8bc Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 28 May 2024 18:19:47 +0400 Subject: [PATCH 002/142] Remove old window activation workaround for X11 systems There seem to be no need in it anymore --- .../media/view/media_view_overlay_widget.cpp | 3 --- Telegram/SourceFiles/window/main_window.cpp | 23 ------------------- Telegram/SourceFiles/window/main_window.h | 2 -- .../window/notifications_manager.cpp | 1 - .../SourceFiles/window/window_controller.cpp | 4 ---- .../SourceFiles/window/window_controller.h | 1 - 6 files changed, 34 deletions(-) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 6d7e8d73b..ecbb7b06f 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -2216,9 +2216,6 @@ void OverlayWidget::close() { return; } hide(); - if (const auto window = Core::App().activeWindow()) { - window->reActivate(); - } _helper->clearState(); } diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index 1c5de5967..e776b7637 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -28,7 +28,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "main/main_session_settings.h" #include "base/options.h" -#include "base/call_delayed.h" #include "base/crc32hash.h" #include "ui/toast/toast.h" #include "ui/widgets/shadow.h" @@ -951,28 +950,6 @@ bool MainWindow::minimizeToTray() { return true; } -void MainWindow::reActivateWindow() { - // X11 is the only platform with unreliable activate requests - if (!Platform::IsX11()) { - return; - } - const auto weak = Ui::MakeWeak(this); - const auto reActivate = [=] { - if (const auto w = weak.data()) { - if (auto f = QApplication::focusWidget()) { - f->clearFocus(); - } - w->activate(); - if (auto f = QApplication::focusWidget()) { - f->clearFocus(); - } - w->setInnerFocus(); - } - }; - crl::on_main(this, reActivate); - base::call_delayed(200, this, reActivate); -} - void MainWindow::showRightColumn(object_ptr widget) { const auto wasWidth = width(); const auto wasRightWidth = _rightColumn ? _rightColumn->width() : 0; diff --git a/Telegram/SourceFiles/window/main_window.h b/Telegram/SourceFiles/window/main_window.h index 67f38567c..7bbbf8e86 100644 --- a/Telegram/SourceFiles/window/main_window.h +++ b/Telegram/SourceFiles/window/main_window.h @@ -98,8 +98,6 @@ public: } void positionUpdated(); - void reActivateWindow(); - void showRightColumn(object_ptr widget); int maximalExtendBy() const; bool canExtendNoMove(int extendBy) const; diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index 052abca8f..460858972 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -1076,7 +1076,6 @@ void Manager::notificationActivated( history->setLocalDraft(std::move(draft)); } window->widget()->showFromTray(); - window->widget()->reActivateWindow(); if (Core::App().passcodeLocked()) { window->widget()->setInnerFocus(); system()->clearAll(); diff --git a/Telegram/SourceFiles/window/window_controller.cpp b/Telegram/SourceFiles/window/window_controller.cpp index 19c12c30e..abe2fa3dc 100644 --- a/Telegram/SourceFiles/window/window_controller.cpp +++ b/Telegram/SourceFiles/window/window_controller.cpp @@ -444,10 +444,6 @@ void Controller::activate() { _widget.activate(); } -void Controller::reActivate() { - _widget.reActivateWindow(); -} - void Controller::updateIsActiveFocus() { _isActiveTimer.callOnce(sessionController() ? sessionController()->session().serverConfig().onlineFocusTimeout diff --git a/Telegram/SourceFiles/window/window_controller.h b/Telegram/SourceFiles/window/window_controller.h index cd5979006..eadd32e2f 100644 --- a/Telegram/SourceFiles/window/window_controller.h +++ b/Telegram/SourceFiles/window/window_controller.h @@ -119,7 +119,6 @@ public: } void activate(); - void reActivate(); void updateIsActiveFocus(); void updateIsActiveBlur(); void updateIsActive(); From d79da3d884a195cedd9114bf16583d8930d69f30 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 28 May 2024 18:20:24 +0400 Subject: [PATCH 003/142] Remove old notification descturction workaround for old X11 systems There seem to be no need in it anymore --- .../settings/settings_notifications.cpp | 16 ---------------- .../window/notifications_manager_default.cpp | 13 +------------ .../window/notifications_manager_default.h | 2 -- 3 files changed, 1 insertion(+), 30 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_notifications.cpp b/Telegram/SourceFiles/settings/settings_notifications.cpp index fdfe37148..1a04419be 100644 --- a/Telegram/SourceFiles/settings/settings_notifications.cpp +++ b/Telegram/SourceFiles/settings/settings_notifications.cpp @@ -28,7 +28,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_specific.h" #include "platform/platform_notifications_manager.h" #include "base/platform/base_platform_info.h" -#include "base/call_delayed.h" #include "mainwindow.h" #include "core/application.h" #include "main/main_session.h" @@ -133,13 +132,10 @@ private: void startAnimation(); void animationCallback(); - void destroyDelayed(); - NotificationsCount *_owner; QPixmap _cache; Ui::Animations::Simple _opacity; bool _hiding = false; - bool _deleted = false; }; @@ -665,18 +661,6 @@ void NotificationsCount::SampleWidget::animationCallback() { _owner->removeSample(this); } hide(); - destroyDelayed(); - } -} - -void NotificationsCount::SampleWidget::destroyDelayed() { - if (_deleted) return; - _deleted = true; - - // Ubuntu has a lag if deleteLater() called immediately. - if constexpr (Platform::IsLinux()) { - base::call_delayed(1000, this, [this] { delete this; }); - } else { deleteLater(); } } diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index 2ce56b113..7b4ba2306 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -496,22 +496,11 @@ Widget::Widget( _a_opacity.start([this] { opacityAnimationCallback(); }, 0., 1., st::notifyFastAnim); } -void Widget::destroyDelayed() { - hide(); - if (_deleted) return; - _deleted = true; - - // Ubuntu has a lag if a fully transparent widget is destroyed immediately. - base::call_delayed(1000, this, [this] { - manager()->removeWidget(this); - }); -} - void Widget::opacityAnimationCallback() { updateOpacity(); update(); if (!_a_opacity.animating() && _hiding) { - destroyDelayed(); + manager()->removeWidget(this); } } diff --git a/Telegram/SourceFiles/window/notifications_manager_default.h b/Telegram/SourceFiles/window/notifications_manager_default.h index b8e582246..5bd4344d8 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.h +++ b/Telegram/SourceFiles/window/notifications_manager_default.h @@ -171,7 +171,6 @@ protected: private: void opacityAnimationCallback(); - void destroyDelayed(); void moveByShift(); void hideAnimated(float64 duration, const anim::transition &func); bool shiftAnimationCallback(crl::time now); @@ -179,7 +178,6 @@ private: const not_null _manager; bool _hiding = false; - bool _deleted = false; base::binary_guard _hidingDelayed; Ui::Animations::Simple _a_opacity; From 8ad5117495b940912f12c94e5b9f97988c0125af Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 15 Jun 2024 20:55:09 +0400 Subject: [PATCH 004/142] Update lib_ui. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index e9fb40c0d..599b4d051 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit e9fb40c0d3ef6f05bfef0f4395c8b2d81bc9823a +Subproject commit 599b4d0517dcbd70e2367aa7af0e8986e6c22052 From 501cae220056429f13a2ba0f8d93f3b864754236 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 6 Jun 2024 20:57:09 +0400 Subject: [PATCH 005/142] Change InputField::font to InputField::style in styles. --- Telegram/SourceFiles/boxes/boxes.style | 7 ++----- Telegram/SourceFiles/boxes/max_invite_box.cpp | 4 ++-- Telegram/SourceFiles/calls/calls.style | 1 - .../SourceFiles/chat_helpers/chat_helpers.style | 2 +- .../SourceFiles/chat_helpers/message_field.cpp | 16 ++++++---------- Telegram/SourceFiles/dialogs/dialogs.style | 2 +- Telegram/SourceFiles/info/info.style | 4 +--- Telegram/SourceFiles/intro/intro.style | 4 +++- Telegram/SourceFiles/passport/passport.style | 4 ++-- .../SourceFiles/payments/ui/payments_field.cpp | 4 ++-- Telegram/SourceFiles/settings/settings.style | 6 +----- Telegram/SourceFiles/ui/chat/chat.style | 2 +- Telegram/SourceFiles/ui/countryinput.cpp | 4 ++-- Telegram/SourceFiles/ui/widgets/color_editor.cpp | 4 ++-- .../ui/widgets/fields/special_fields.cpp | 4 ++-- .../window/themes/window_theme_preview.cpp | 4 ++-- Telegram/SourceFiles/window/window.style | 4 +--- Telegram/lib_ui | 2 +- 18 files changed, 32 insertions(+), 46 deletions(-) diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index cfd123c93..f17389f1b 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -237,7 +237,7 @@ shareColumnSkip: 6px; shareActivateDuration: 150; shareScrollDuration: 300; shareComment: InputField(defaultInputField) { - font: normalFont; + style: defaultTextStyle; textMargins: margins(8px, 8px, 8px, 6px); heightMin: 36px; heightMax: 72px; @@ -585,7 +585,7 @@ groupStickersRemovePosition: point(6px, 6px); groupStickersFieldPadding: margins(8px, 6px, 8px, 6px); groupStickersField: InputField(defaultMultiSelectSearchField) { placeholderFont: boxTextFont; - font: boxTextFont; + style: boxTextStyle; placeholderMargins: margins(0px, 0px, 0px, 0px); textMargins: margins(0px, 7px, 0px, 0px); textBg: boxBg; @@ -672,7 +672,6 @@ themesMenuToggle: IconButton(defaultIconButton) { themesMenuPosition: point(-2px, 25px); createPollField: InputField(defaultInputField) { - font: boxTextFont; textMargins: margins(0px, 4px, 0px, 4px); textAlign: align(left); heightMin: 36px; @@ -877,7 +876,6 @@ scheduleDateField: InputField(defaultInputField) { placeholderScale: 0.; heightMin: 30px; textAlign: align(top); - font: font(14px); } scheduleTimeField: InputField(scheduleDateField) { border: 0px; @@ -905,7 +903,6 @@ muteBoxTimeField: InputField(scheduleDateField) { placeholderScale: 0.; heightMin: 30px; textAlign: align(left); - font: font(14px); } muteBoxTimeFieldPadding: margins(5px, 0px, 5px, 0px); diff --git a/Telegram/SourceFiles/boxes/max_invite_box.cpp b/Telegram/SourceFiles/boxes/max_invite_box.cpp index b6230b673..a7c1f8408 100644 --- a/Telegram/SourceFiles/boxes/max_invite_box.cpp +++ b/Telegram/SourceFiles/boxes/max_invite_box.cpp @@ -133,8 +133,8 @@ void MaxInviteBox::paintEvent(QPaintEvent *e) { auto option = QTextOption(style::al_left); option.setWrapMode(QTextOption::WrapAnywhere); p.setFont(_linkOver - ? st::defaultInputField.font->underline() - : st::defaultInputField.font); + ? st::defaultInputField.style.font->underline() + : st::defaultInputField.style.font); p.setPen(st::defaultLinkButton.color); const auto inviteLinkText = _channel->inviteLink().isEmpty() ? tr::lng_group_invite_create(tr::now) diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index cc009ad43..a133bba68 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1393,7 +1393,6 @@ groupCallScheduleDateField: InputField(groupCallField) { placeholderScale: 0.; heightMin: 30px; textAlign: align(top); - font: font(14px); } groupCallScheduleTimeField: InputField(groupCallScheduleDateField) { textBg: groupCallMembersBg; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index b3f7e73c9..ef96e4366 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -990,7 +990,7 @@ historyUnreadReactions: TwoIconButton(historyToDown) { historyUnreadThingsSkip: 4px; historyComposeField: InputField(defaultInputField) { - font: normalFont; + style: defaultTextStyle; textMargins: margins(0px, 0px, 0px, 0px); textAlign: align(left); textFg: historyComposeAreaFg; diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 779bfb4d5..bb328564c 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -569,10 +569,10 @@ void InitMessageFieldFade( Ui::DestroyChild(b.data()); }, topFade->lifetime()); + const auto descent = field->st().style.font->descent; topFade->show(); - bottomFade->showOn( - field->scrollTop().value( - ) | rpl::map([field, descent = field->st().font->descent](int scroll) { + bottomFade->showOn(field->scrollTop().value( + ) | rpl::map([field, descent](int scroll) { return (scroll + descent) < field->scrollTopMax(); }) | rpl::distinct_until_changed()); } @@ -766,11 +766,7 @@ bool MessageLinksParser::eventFilter(QObject *object, QEvent *event) { const auto text = static_cast(event)->text(); if (!text.isEmpty() && text.size() < 3) { const auto ch = text[0]; - if (false - || ch == '\n' - || ch == '\r' - || ch.isSpace() - || ch == QChar::LineSeparator) { + if (IsSpace(ch)) { _timer.callOnce(0); } } @@ -1117,8 +1113,8 @@ void SelectTextInFieldWithMargins( auto textCursor = field->textCursor(); // Try to set equal margins for top and bottom sides. const auto charsCountInLine = field->width() - / field->st().font->width('W'); - const auto linesCount = (field->height() / field->st().font->height); + / field->st().style.font->width('W'); + const auto linesCount = field->height() / field->st().style.font->height; const auto selectedLines = (selection.to - selection.from) / charsCountInLine; constexpr auto kMinDiff = ushort(3); diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index dee018efd..279defe7d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -285,7 +285,7 @@ dialogsFilter: InputField(defaultInputField) { borderRadius: 18px; borderDenominator: 2; - font: normalFont; + style: defaultTextStyle; heightMin: 35px; } diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index ab16a1bd1..8e44a7a79 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -701,7 +701,7 @@ manageGroupReactionsField: InputField(defaultInputField) { placeholderScale: 0.; placeholderFont: normalFont; placeholderShift: -50px; - font: normalFont; + style: defaultTextStyle; heightMin: 36px; heightMax: 158px; } @@ -764,8 +764,6 @@ editPeerDescription: InputField(defaultInputField) { borderActive: 0px; heightMin: 32px; - - font: boxTextFont; } editPeerDescriptionMargins: margins(22px, 3px, 22px, 2px); editPeerPrivaciesMargins: margins(15px, 7px, 22px, 0px); diff --git a/Telegram/SourceFiles/intro/intro.style b/Telegram/SourceFiles/intro/intro.style index bedbad748..ae504be09 100644 --- a/Telegram/SourceFiles/intro/intro.style +++ b/Telegram/SourceFiles/intro/intro.style @@ -96,7 +96,9 @@ introPhoneTop: 6px; introLinkTop: 24px; introCountry: InputField(defaultInputField) { textMargins: margins(3px, 27px, 3px, 6px); - font: font(16px); + style: TextStyle(defaultTextStyle) { + font: font(16px); + } width: 300px; heightMin: 61px; } diff --git a/Telegram/SourceFiles/passport/passport.style b/Telegram/SourceFiles/passport/passport.style index c7e2a9466..c5bf74443 100644 --- a/Telegram/SourceFiles/passport/passport.style +++ b/Telegram/SourceFiles/passport/passport.style @@ -159,7 +159,7 @@ passportDetailsField: InputField(defaultInputField) { placeholderScale: 0.; placeholderFont: normalFont; heightMin: 32px; - font: normalFont; + style: defaultTextStyle; } passportDetailsDateField: InputField(passportDetailsField) { textMargins: margins(2px, 8px, 2px, 0px); @@ -178,7 +178,7 @@ passportDetailsSeparator: FlatLabel(passportPasswordLabelBold) { } passportDetailsSeparatorPadding: margins(5px, 8px, 5px, 0px); passportContactField: InputField(defaultInputField) { - font: normalFont; + style: defaultTextStyle; } passportDetailsFieldLeft: 116px; passportDetailsFieldTop: 2px; diff --git a/Telegram/SourceFiles/payments/ui/payments_field.cpp b/Telegram/SourceFiles/payments/ui/payments_field.cpp index 1314674ce..e5fbcb34e 100644 --- a/Telegram/SourceFiles/payments/ui/payments_field.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_field.cpp @@ -305,7 +305,7 @@ struct SimpleFieldState { .st = st::paymentsMoneyField, }); const auto &rule = state->rule; - state->currencySkip = rule.space ? state->st.font->spacew : 0; + state->currencySkip = rule.space ? state->st.style.font->spacew : 0; state->currencyText = ((!rule.left && rule.space) ? QString(QChar(' ')) : QString()) + (*rule.international @@ -343,7 +343,7 @@ struct SimpleFieldState { } const auto updateRight = [=] { const auto text = result->getLastText(); - const auto width = state->st.font->width(text); + const auto width = state->st.style.font->width(text); const auto &rule = state->rule; const auto symbol = QChar(rule.decimal); const auto decimal = text.indexOf(symbol); diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index d931f1df7..5fb976285 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -214,8 +214,6 @@ settingsBio: InputField(defaultInputField) { borderActive: 0px; heightMin: 32px; - - font: boxTextFont; } settingsBioMargins: margins(22px, 6px, 22px, 4px); @@ -309,8 +307,6 @@ settingsDeviceName: InputField(defaultInputField) { placeholderFont: normalFont; heightMin: 29px; - - font: boxTextFont; } dictionariesSectionButton: SettingsButton(settingsUpdateToggle) { @@ -677,5 +673,5 @@ settingsChatLinkField: InputField(defaultInputField) { heightMin: 32px; - font: normalFont; + style: defaultTextStyle; } diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 732697926..7f8207521 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1162,5 +1162,5 @@ factcheckField: InputField(defaultInputField) { heightMin: 24px; - font: normalFont; + style: defaultTextStyle; } diff --git a/Telegram/SourceFiles/ui/countryinput.cpp b/Telegram/SourceFiles/ui/countryinput.cpp index a9c9e1add..bdf77d1c1 100644 --- a/Telegram/SourceFiles/ui/countryinput.cpp +++ b/Telegram/SourceFiles/ui/countryinput.cpp @@ -55,7 +55,7 @@ void CountryInput::paintEvent(QPaintEvent *e) { st::introCountryIconPosition.y(), width()); - p.setFont(_st.font); + p.setFont(_st.style.font); p.setPen(_st.textFg); p.drawText(rect().marginsRemoved(_st.textMargins), _text, _st.textAlign); } @@ -159,7 +159,7 @@ rpl::producer CountryInput::codeChanged() const { } void CountryInput::setText(const QString &newText) { - _text = _st.font->elided( + _text = _st.style.font->elided( newText, width() - _st.textMargins.left() - _st.textMargins.right()); } diff --git a/Telegram/SourceFiles/ui/widgets/color_editor.cpp b/Telegram/SourceFiles/ui/widgets/color_editor.cpp index 3141faef3..bf2ba26fc 100644 --- a/Telegram/SourceFiles/ui/widgets/color_editor.cpp +++ b/Telegram/SourceFiles/ui/widgets/color_editor.cpp @@ -704,7 +704,7 @@ void ColorEditor::Field::correctValue( } void ColorEditor::Field::paintAdditionalPlaceholder(QPainter &p) { - p.setFont(_st.font); + p.setFont(_st.style.font); p.setPen(_st.placeholderFg); const auto inner = QRect( _st.textMargins.right(), @@ -829,7 +829,7 @@ void ColorEditor::ResultField::correctValue( } void ColorEditor::ResultField::paintAdditionalPlaceholder(QPainter &p) { - p.setFont(_st.font); + p.setFont(_st.style.font); p.setPen(_st.placeholderFg); p.drawText( QRect( diff --git a/Telegram/SourceFiles/ui/widgets/fields/special_fields.cpp b/Telegram/SourceFiles/ui/widgets/fields/special_fields.cpp index 81b1b1a0d..0b7487caf 100644 --- a/Telegram/SourceFiles/ui/widgets/fields/special_fields.cpp +++ b/Telegram/SourceFiles/ui/widgets/fields/special_fields.cpp @@ -267,14 +267,14 @@ UsernameInput::UsernameInput( void UsernameInput::setLinkPlaceholder(const QString &placeholder) { _linkPlaceholder = placeholder; if (!_linkPlaceholder.isEmpty()) { - setTextMargins(style::margins(_st.textMargins.left() + _st.font->width(_linkPlaceholder), _st.textMargins.top(), _st.textMargins.right(), _st.textMargins.bottom())); + setTextMargins(style::margins(_st.textMargins.left() + _st.style.font->width(_linkPlaceholder), _st.textMargins.top(), _st.textMargins.right(), _st.textMargins.bottom())); setPlaceholderHidden(true); } } void UsernameInput::paintAdditionalPlaceholder(QPainter &p) { if (!_linkPlaceholder.isEmpty()) { - p.setFont(_st.font); + p.setFont(_st.style.font); p.setPen(_st.placeholderFg); p.drawText(QRect(_st.textMargins.left(), _st.textMargins.top(), width(), height() - _st.textMargins.top() - _st.textMargins.bottom()), _linkPlaceholder, style::al_topleft); } diff --git a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp index d486e81b3..42f1abb3c 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp @@ -604,7 +604,7 @@ void Generator::paintComposeArea() { _p->setClipRect(field); _p->save(); - _p->setFont(st::historyComposeField.font); + _p->setFont(st::historyComposeField.style.font); _p->setPen(st::historyComposeField.placeholderFg[_palette]); auto placeholderRect = QRect( @@ -658,7 +658,7 @@ void Generator::paintDialogs() { _p->save(); _p->setClipRect(filter); auto phRect = QRect(filter.x() + st::dialogsFilter.textMargins.left() + st::dialogsFilter.placeholderMargins.left(), filter.y() + st::dialogsFilter.textMargins.top() + st::dialogsFilter.placeholderMargins.top(), filter.width() - st::dialogsFilter.textMargins.left() - st::dialogsFilter.textMargins.right(), filter.height() - st::dialogsFilter.textMargins.top() - st::dialogsFilter.textMargins.bottom()); - _p->setFont(st::dialogsFilter.font); + _p->setFont(st::dialogsFilter.style.font); _p->setPen(st::dialogsFilter.placeholderFg[_palette]); _p->drawText(phRect, tr::lng_dlg_filter(tr::now), QTextOption(st::dialogsFilter.placeholderAlign)); _p->restore(); diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index 8b13c0a36..37654c990 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -61,7 +61,7 @@ notifyActionsDuration: 200; notifyHideAllHeight: 36px; notifyReplyArea: InputField(defaultInputField) { - font: normalFont; + style: defaultTextStyle; textMargins: margins(8px, 8px, 8px, 6px); heightMin: 36px; heightMax: 72px; @@ -235,8 +235,6 @@ createThemeLink: InputField(defaultInputField) { placeholderFont: boxTextFont; heightMin: 34px; - - font: boxTextFont; } windowFiltersWidth: 72px; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 599b4d051..924301cd6 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 599b4d0517dcbd70e2367aa7af0e8986e6c22052 +Subproject commit 924301cd62d918ef25ab81d758ed4b2fff903f2e From c1f36d43d0441d8cecb53ead6ae7a72df72f0e80 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Jun 2024 14:15:18 +0400 Subject: [PATCH 006/142] Support modern blockquote/pre display. --- .../chat_helpers/chat_helpers.style | 27 ++++++++++++++++++- .../chat_helpers/message_field.cpp | 10 +++++++ Telegram/SourceFiles/ui/chat/chat.style | 27 ++----------------- Telegram/lib_ui | 2 +- 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index ef96e4366..6b69aa136 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -989,8 +989,33 @@ historyUnreadReactions: TwoIconButton(historyToDown) { } historyUnreadThingsSkip: 4px; +historyQuoteStyle: QuoteStyle(defaultQuoteStyle) { + padding: margins(10px, 2px, 4px, 2px); + verticalSkip: 4px; + outline: 3px; + outlineShift: 2px; + radius: 5px; +} +historyTextStyle: TextStyle(defaultTextStyle) { + blockquote: QuoteStyle(historyQuoteStyle) { + padding: margins(10px, 2px, 20px, 2px); + icon: icon{{ "chat/mini_quote", windowFg }}; + iconPosition: point(4px, 4px); + expand: icon{{ "intro_country_dropdown", windowFg }}; + expandPosition: point(6px, 4px); + collapse: icon{{ "intro_country_dropdown-flip_vertical", windowFg }}; + collapsePosition: point(6px, 4px); + } + pre: QuoteStyle(historyQuoteStyle) { + header: 20px; + headerPosition: point(10px, 2px); + scrollable: true; + icon: icon{{ "chat/mini_copy", windowFg }}; + iconPosition: point(4px, 2px); + } +} historyComposeField: InputField(defaultInputField) { - style: defaultTextStyle; + style: historyTextStyle; textMargins: margins(0px, 0px, 0px, 0px); textAlign: align(left); textFg: historyComposeAreaFg; diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index bb328564c..d65a7acf9 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/qthelp_regex.h" #include "base/qthelp_url.h" #include "base/event_filter.h" +#include "ui/chat/chat_style.h" #include "ui/layers/generic_box.h" #include "ui/rect.h" #include "core/shortcuts.h" @@ -344,6 +345,15 @@ void InitMessageFieldHandlers( DefaultEditLinkCallback(show, field, fieldStyle)); InitSpellchecker(show, field, fieldStyle != nullptr); } + const auto style = field->lifetime().make_state( + session->colorIndicesValue()); + field->setPreCache([=] { + return style->messageStyle(false, false).preCache.get(); + }); + field->setBlockquoteCache([=] { + const auto colorIndex = session->user()->colorIndex(); + return style->coloredQuoteCache(false, colorIndex).get(); + }); } [[nodiscard]] bool IsGoodFactcheckUrl(QStringView url) { diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 7f8207521..e4ff33434 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -54,31 +54,8 @@ msgDateDelta: point(2px, 5px); msgDateImgDelta: 4px; msgDateImgPadding: point(8px, 2px); -messageQuoteStyle: QuoteStyle(defaultQuoteStyle) { - padding: margins(10px, 2px, 4px, 2px); - verticalSkip: 4px; - outline: 3px; - outlineShift: 2px; - radius: 5px; -} -messageTextStyle: TextStyle(defaultTextStyle) { - blockquote: QuoteStyle(messageQuoteStyle) { - padding: margins(10px, 2px, 20px, 2px); - icon: icon{{ "chat/mini_quote", windowFg }}; - iconPosition: point(4px, 4px); - expand: icon{{ "intro_country_dropdown", windowFg }}; - expandPosition: point(6px, 4px); - collapse: icon{{ "intro_country_dropdown-flip_vertical", windowFg }}; - collapsePosition: point(6px, 4px); - } - pre: QuoteStyle(messageQuoteStyle) { - header: 20px; - headerPosition: point(10px, 2px); - scrollable: true; - icon: icon{{ "chat/mini_copy", windowFg }}; - iconPosition: point(4px, 2px); - } -} +messageQuoteStyle: historyQuoteStyle; +messageTextStyle: historyTextStyle; historyPagePreview: QuoteStyle(messageQuoteStyle) { padding: margins(10px, 5px, 7px, 7px); } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 924301cd6..d4d51136c 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 924301cd62d918ef25ab81d758ed4b2fff903f2e +Subproject commit d4d51136cd5ff54b84b2863a7ece39693b5ce522 From fa8ed186d86d406d03f1b02b1ce728da9a0a228a Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 10 Jun 2024 10:55:44 +0400 Subject: [PATCH 007/142] Improve field fade conditions. --- .../chat_helpers/message_field.cpp | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index d65a7acf9..cd7180218 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -580,11 +580,21 @@ void InitMessageFieldFade( }, topFade->lifetime()); const auto descent = field->st().style.font->descent; - topFade->show(); - bottomFade->showOn(field->scrollTop().value( - ) | rpl::map([field, descent](int scroll) { - return (scroll + descent) < field->scrollTopMax(); - }) | rpl::distinct_until_changed()); + rpl::merge( + field->changes(), + field->scrollTop().changes() | rpl::to_empty, + field->sizeValue() | rpl::to_empty + ) | rpl::start_with_next([=] { + const auto topHidden = !field->scrollTop().current(); + if (topFade->isHidden() != topHidden) { + topFade->setVisible(!topHidden); + } + const auto adjusted = field->scrollTop().current() + descent; + const auto bottomHidden = (adjusted >= field->scrollTopMax()); + if (bottomFade->isHidden() != bottomHidden) { + bottomFade->setVisible(!bottomHidden); + } + }, topFade->lifetime()); } InlineBotQuery ParseInlineBotQuery( From 5e8c3fb146df8d693ecccfb70d9afbe427bfd6be Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 10 Jun 2024 18:05:37 +0400 Subject: [PATCH 008/142] Allow editing syntax highlighting language. --- Telegram/Resources/langs/lang.strings | 3 + .../SourceFiles/boxes/create_poll_box.cpp | 11 ++- .../chat_helpers/message_field.cpp | 77 ++++++++++++++++--- .../SourceFiles/chat_helpers/message_field.h | 3 + .../history_view_compose_controls.cpp | 1 + Telegram/lib_ui | 2 +- 6 files changed, 86 insertions(+), 11 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index fa1aab1b8..7a3c42b06 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3582,6 +3582,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_formatting_link_text" = "Text"; "lng_formatting_link_url" = "URL"; "lng_formatting_link_create" = "Create"; +"lng_formatting_code_title" = "Code Language"; +"lng_formatting_code_language" = "Language for syntax highlighting."; +"lng_formatting_code_auto" = "Auto-Detect"; "lng_text_copied" = "Text copied to clipboard."; "lng_code_copied" = "Block copied to clipboard."; diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp index 46138e3fd..9ff85e099 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.cpp +++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp @@ -1044,7 +1044,16 @@ not_null CreatePollBox::setupSolution( solution->setInstantReplaces(Ui::InstantReplaces::Default()); solution->setInstantReplacesEnabled( Core::App().settings().replaceEmojiValue()); - solution->setMarkdownReplacesEnabled(true); + solution->setMarkdownReplacesEnabled(rpl::single( + Ui::MarkdownEnabledState{ Ui::MarkdownEnabled{ { + Ui::InputField::kTagBold, + Ui::InputField::kTagItalic, + Ui::InputField::kTagUnderline, + Ui::InputField::kTagStrikeOut, + Ui::InputField::kTagCode, + Ui::InputField::kTagSpoiler, + } } } + )); solution->setEditLinkCallback( DefaultEditLinkCallback(_controller->uiShow(), solution)); solution->customTab(true); diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index cd7180218..d36d2c465 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_boxes.h" #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" +#include "styles/style_settings.h" #include "base/qt/qt_common_adapters.h" #include @@ -59,6 +60,7 @@ using EditLinkSelection = Ui::InputField::EditLinkSelection; constexpr auto kParseLinksTimeout = crl::time(1000); constexpr auto kTypesDuration = 4 * crl::time(1000); +constexpr auto kCodeLanguageLimit = 32; // For mention / custom emoji tags save and validate selfId, // ignore tags for different users. @@ -223,6 +225,51 @@ void EditLinkBox( }, text->lifetime()); } +void EditCodeLanguageBox( + not_null box, + QString now, + Fn save) { + Expects(save != nullptr); + + box->setTitle(tr::lng_formatting_code_title()); + box->addRow(object_ptr( + box, + tr::lng_formatting_code_language(), + st::settingsAddReplyLabel)); + const auto field = box->addRow(object_ptr( + box, + st::settingsAddReplyField, + tr::lng_formatting_code_auto(), + now.trimmed())); + box->setFocusCallback([=] { + field->setFocusFast(); + }); + field->selectAll(); + field->setMaxLength(kCodeLanguageLimit); + + Ui::AddLengthLimitLabel(field, kCodeLanguageLimit); + + const auto callback = [=] { + const auto name = field->getLastText().trimmed(); + const auto check = QRegularExpression("^[a-zA-Z0-9\\+\\-]+$"); + if (check.match(name).hasMatch()) { + auto weak = Ui::MakeWeak(box); + save(name); + if (const auto strong = weak.data()) { + strong->closeBox(); + } + } else { + field->showError(); + } + }; + field->submits( + ) | rpl::start_with_next(callback, field->lifetime()); + box->addButton(tr::lng_settings_save(), callback); + box->addButton(tr::lng_cancel(), [=] { + box->closeBox(); + }); +} + TextWithEntities StripSupportHashtag(TextWithEntities text) { static const auto expression = QRegularExpression( u"\\n?#tsf[a-z0-9_-]*[\\s#a-z0-9_-]*$"_q, @@ -321,6 +368,13 @@ Fn save)> DefaultEditLanguageCallback( + std::shared_ptr show) { + return [=](QString now, Fn save) { + show->showBox(Box(EditCodeLanguageBox, now, save)); + }; +} + void InitMessageFieldHandlers( not_null session, std::shared_ptr show, @@ -343,6 +397,7 @@ void InitMessageFieldHandlers( if (show) { field->setEditLinkCallback( DefaultEditLinkCallback(show, field, fieldStyle)); + field->setEditLanguageCallback(DefaultEditLanguageCallback(show)); InitSpellchecker(show, field, fieldStyle != nullptr); } const auto style = field->lifetime().make_state( @@ -585,15 +640,19 @@ void InitMessageFieldFade( field->scrollTop().changes() | rpl::to_empty, field->sizeValue() | rpl::to_empty ) | rpl::start_with_next([=] { - const auto topHidden = !field->scrollTop().current(); - if (topFade->isHidden() != topHidden) { - topFade->setVisible(!topHidden); - } - const auto adjusted = field->scrollTop().current() + descent; - const auto bottomHidden = (adjusted >= field->scrollTopMax()); - if (bottomFade->isHidden() != bottomHidden) { - bottomFade->setVisible(!bottomHidden); - } + // InputField::changes fires before the auto-resize is being applied, + // so for the scroll values to be accurate we enqueue the check. + InvokeQueued(field, [=] { + const auto topHidden = !field->scrollTop().current(); + if (topFade->isHidden() != topHidden) { + topFade->setVisible(!topHidden); + } + const auto adjusted = field->scrollTop().current() + descent; + const auto bottomHidden = (adjusted >= field->scrollTopMax()); + if (bottomFade->isHidden() != bottomHidden) { + bottomFade->setVisible(!bottomHidden); + } + }); }, topFade->lifetime()); } diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h index 1e67642a7..7efdb7d18 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.h +++ b/Telegram/SourceFiles/chat_helpers/message_field.h @@ -35,6 +35,7 @@ class Show; namespace Ui { class PopupMenu; +class Show; } // namespace Ui [[nodiscard]] QString PrepareMentionTag(not_null user); @@ -51,6 +52,8 @@ Fn show, not_null field, const style::InputField *fieldStyle = nullptr); +Fn save)> DefaultEditLanguageCallback( + std::shared_ptr show); void InitMessageFieldHandlers( not_null session, std::shared_ptr show, // may be null diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index cc4a91a1f..16a559689 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1658,6 +1658,7 @@ void ComposeControls::initField() { InitMessageFieldFade(_field, _st.field.textBg); _field->setEditLinkCallback( DefaultEditLinkCallback(_show, _field, &_st.boxField)); + _field->setEditLanguageCallback(DefaultEditLanguageCallback(_show)); initAutocomplete(); const auto allow = [=](not_null emoji) { return _history diff --git a/Telegram/lib_ui b/Telegram/lib_ui index d4d51136c..20caa93a8 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit d4d51136cd5ff54b84b2863a7ece39693b5ce522 +Subproject commit 20caa93a8d2b5b279f36eb19d9200be071420ba3 From ec40292cbf336187fa3cf4dbaf6e4dd9f6c41901 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 12 Jun 2024 21:22:06 +0400 Subject: [PATCH 009/142] Allow editing of collapsed quotes. --- .../boxes/peers/edit_peer_reactions.cpp | 13 ++++++-- .../chat_helpers/message_field.cpp | 32 +++++++++++++------ .../SourceFiles/chat_helpers/message_field.h | 2 +- Telegram/SourceFiles/core/ui_integration.cpp | 2 +- Telegram/SourceFiles/core/ui_integration.h | 2 +- Telegram/lib_ui | 2 +- 6 files changed, 37 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp index 0873426e5..8905c33c1 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/event_filter.h" #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_selector.h" +#include "core/ui_integration.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_document.h" @@ -351,8 +352,8 @@ object_ptr AddReactionsSelector( const auto customEmojiPaused = [controller = args.controller] { return controller->isGifPausedAtLeastFor(PauseReason::Layer); }; - raw->setCustomEmojiFactory([=](QStringView data, Fn update) - -> std::unique_ptr { + auto factory = [=](QStringView data, Fn update) + -> std::unique_ptr { const auto id = Data::ParseCustomEmojiData(data); auto result = owner->customEmojiManager().create( data, @@ -364,7 +365,13 @@ object_ptr AddReactionsSelector( } using namespace Ui::Text; return std::make_unique(std::move(result)); - }, std::move(customEmojiPaused)); + }; + raw->setCustomTextContext([=](Fn repaint) { + return std::any(Core::MarkedTextContext{ + .session = session, + .customEmojiRepaint = std::move(repaint), + }); + }, customEmojiPaused, customEmojiPaused, std::move(factory)); const auto callback = args.callback; const auto isCustom = [=](DocumentId id) { diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index d36d2c465..465830fd1 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/shortcuts.h" #include "core/application.h" #include "core/core_settings.h" +#include "core/ui_integration.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/wrap/vertical_layout.h" @@ -322,15 +323,24 @@ TextWithTags PrepareEditText(not_null item) { bool EditTextChanged( not_null item, - const TextWithTags &updated) { + TextWithTags updated) { const auto original = PrepareEditText(item); + auto originalWithEntities = TextWithEntities{ + std::move(original.text), + TextUtilities::ConvertTextTagsToEntities(original.tags) + }; + auto updatedWithEntities = TextWithEntities{ + std::move(updated.text), + TextUtilities::ConvertTextTagsToEntities(updated.tags) + }; + TextUtilities::PrepareForSending(originalWithEntities, 0); + TextUtilities::PrepareForSending(updatedWithEntities, 0); + // Tags can be different for the same entities, because for // animated emoji each tag contains a different random number. // So we compare entities instead of tags. - return (original.text != updated.text) - || (TextUtilities::ConvertTextTagsToEntities(original.tags) - != TextUtilities::ConvertTextTagsToEntities(updated.tags)); + return originalWithEntities != updatedWithEntities; } FnsetTagMimeProcessor( FieldTagMimeProcessor(session, allowPremiumEmoji)); - const auto paused = [customEmojiPaused] { + field->setCustomTextContext([=](Fn repaint) { + return std::any(Core::MarkedTextContext{ + .session = session, + .customEmojiRepaint = std::move(repaint), + }); + }, [customEmojiPaused] { return On(PowerSaving::kEmojiChat) || customEmojiPaused(); - }; - field->setCustomEmojiFactory( - session->data().customEmojiManager().factory(), - std::move(customEmojiPaused)); + }, [customEmojiPaused] { + return On(PowerSaving::kChatSpoiler) || customEmojiPaused(); + }); field->setInstantReplaces(Ui::InstantReplaces::Default()); field->setInstantReplacesEnabled( Core::App().settings().replaceEmojiValue()); diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h index 7efdb7d18..041b54d66 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.h +++ b/Telegram/SourceFiles/chat_helpers/message_field.h @@ -42,7 +42,7 @@ class Show; [[nodiscard]] TextWithTags PrepareEditText(not_null item); [[nodiscard]] bool EditTextChanged( not_null item, - const TextWithTags &updated); + TextWithTags updated); Fn UiIntegration::createCustomEmoji( - const QString &data, + QStringView data, const std::any &context) { const auto my = std::any_cast(&context); if (!my || !my->session) { diff --git a/Telegram/SourceFiles/core/ui_integration.h b/Telegram/SourceFiles/core/ui_integration.h index 36ee45777..a745f0fce 100644 --- a/Telegram/SourceFiles/core/ui_integration.h +++ b/Telegram/SourceFiles/core/ui_integration.h @@ -58,7 +58,7 @@ public: const Ui::Emoji::One *defaultEmojiVariant( const Ui::Emoji::One *emoji) override; std::unique_ptr createCustomEmoji( - const QString &data, + QStringView data, const std::any &context) override; Fn createSpoilerRepaint(const std::any &context) override; bool allowClickHandlerActivation( diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 20caa93a8..29bce3f8f 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 20caa93a8d2b5b279f36eb19d9200be071420ba3 +Subproject commit 29bce3f8ff693e0386617dfd550a225d0ff11c5d From 9a25d2c4139e49312300f0de237725765fb338ca Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 14 Jun 2024 18:04:12 +0400 Subject: [PATCH 010/142] Simplify correct unicode characters counting. --- Telegram/SourceFiles/history/history_widget.cpp | 2 +- .../history/view/controls/history_view_compose_controls.cpp | 5 +++-- Telegram/SourceFiles/settings/settings_information.cpp | 3 +-- Telegram/lib_ui | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index a805c5479..e81fc1bc2 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -7513,7 +7513,7 @@ void HistoryWidget::showPremiumToast(not_null document) { } void HistoryWidget::checkCharsCount() { - _fieldCharsCountManager.setCount(Ui::FieldCharacterCount(_field)); + _fieldCharsCountManager.setCount(Ui::ComputeFieldCharacterCount(_field)); checkCharsLimitation(); } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 16a559689..779f0386b 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -3268,7 +3268,7 @@ not_null ComposeControls::likeAnimationTarget() const { } int ComposeControls::fieldCharacterCount() const { - return Ui::FieldCharacterCount(_field); + return Ui::ComputeFieldCharacterCount(_field); } bool ComposeControls::preventsClose(Fn &&continueCallback) const { @@ -3451,7 +3451,8 @@ void ComposeControls::checkCharsLimitation() { const auto maxCaptionSize = !hasMediaWithCaption ? MaxMessageSize : Data::PremiumLimits(&session()).captionLengthCurrent(); - const auto remove = Ui::FieldCharacterCount(_field) - maxCaptionSize; + const auto remove = Ui::ComputeFieldCharacterCount(_field) + - maxCaptionSize; if (remove > 0) { if (!_charsLimitation) { using namespace Controls; diff --git a/Telegram/SourceFiles/settings/settings_information.cpp b/Telegram/SourceFiles/settings/settings_information.cpp index 9db8d59a9..7be04db17 100644 --- a/Telegram/SourceFiles/settings/settings_information.cpp +++ b/Telegram/SourceFiles/settings/settings_information.cpp @@ -573,8 +573,7 @@ void SetupBio( } changed->fire(*current != text); const auto limit = self->isPremium() ? premiumLimit : defaultLimit; - const auto countLeft = limit - - bio->lastTextSizeWithoutSurrogatePairsCount(); + const auto countLeft = limit - Ui::ComputeFieldCharacterCount(bio); countdown->setText(QString::number(countLeft)); countdown->setTextColorOverride( countLeft < 0 ? st::boxTextFgError->c : std::optional()); diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 29bce3f8f..179a7ec1c 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 29bce3f8ff693e0386617dfd550a225d0ff11c5d +Subproject commit 179a7ec1c1c4310e1c8c935e466d23cbfbe07221 From f748988ae3d27aae00f0efff403cda1099ae0996 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 15 Jun 2024 20:59:19 +0400 Subject: [PATCH 011/142] Update Qt patches. --- Telegram/build/docker/centos_env/Dockerfile | 2 +- Telegram/build/prepare/prepare.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 02c3f9f3e..03ed611f2 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -51,7 +51,7 @@ FROM builder AS patches RUN git init patches \ && cd patches \ && git remote add origin {{ GIT }}/desktop-app/patches.git \ - && git fetch --depth=1 origin 803f1c2630f5eb0d3b00ba3f095b3079c0533156 \ + && git fetch --depth=1 origin 8639205c2095a3f015449f8995fbfe16405218b8 \ && git reset --hard FETCH_HEAD \ && rm -rf .git diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index a705fa843..011476c50 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -435,7 +435,7 @@ if customRunCommand: stage('patches', """ git clone https://github.com/desktop-app/patches.git cd patches - git checkout c237d12bcd + git checkout 8639205c20 """) stage('msys64', """ From f37d056c14caf43befa8d1f7f779b091f888dca6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 15 Jun 2024 21:06:23 +0400 Subject: [PATCH 012/142] Beta version 5.1.8. - Support nice blockquotes and code blocks edition when composing messages. - Support collapsing blockquotes and specifying syntax highlight language. - Support nice spoiler animation in the message composing input field. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 6 +++--- Telegram/build/version | 10 +++++----- changelog.txt | 6 ++++++ 6 files changed, 23 insertions(+), 17 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index fed726359..0fd7008c4 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.1.8.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index fb620ebce..642b222da 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,1,7,0 - PRODUCTVERSION 5,1,7,0 + FILEVERSION 5,1,8,0 + PRODUCTVERSION 5,1,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.1.7.0" + VALUE "FileVersion", "5.1.8.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.1.7.0" + VALUE "ProductVersion", "5.1.8.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 7f599ef59..48faaa4b8 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,1,7,0 - PRODUCTVERSION 5,1,7,0 + FILEVERSION 5,1,8,0 + PRODUCTVERSION 5,1,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.1.7.0" + VALUE "FileVersion", "5.1.8.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.1.7.0" + VALUE "ProductVersion", "5.1.8.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 960408a65..751c3b026 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 = 5001007; -constexpr auto AppVersionStr = "5.1.7"; -constexpr auto AppBetaVersion = false; +constexpr auto AppVersion = 5001008; +constexpr auto AppVersionStr = "5.1.8"; +constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 6f0f009fb..37dc61b49 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5001007 +AppVersion 5001008 AppVersionStrMajor 5.1 -AppVersionStrSmall 5.1.7 -AppVersionStr 5.1.7 -BetaChannel 0 +AppVersionStrSmall 5.1.8 +AppVersionStr 5.1.8 +BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 5.1.7 +AppVersionOriginal 5.1.8.beta diff --git a/changelog.txt b/changelog.txt index 5eb0f2e7e..a5ea6efcc 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +5.1.8 beta (15.06.24) + +- Support nice blockquotes and code blocks edition when composing messages. +- Support collapsing blockquotes and specifying syntax highlight language. +- Support nice spoiler animation in the message composing input field. + 5.1.7 (14.06.24) - Fix recently searched hashtags in chats search. From 90068f626199154d05ebc8be1771f47c386df210 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 15 Jun 2024 21:16:55 +0400 Subject: [PATCH 013/142] Beta version 5.1.8: Update tg_owt on Linux. --- Telegram/build/docker/centos_env/Dockerfile | 2 +- snap/snapcraft.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 03ed611f2..70f19b2c6 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -761,7 +761,7 @@ COPY --link --from=pipewire {{ LibrariesPath }}/pipewire-cache / RUN git init tg_owt \ && cd tg_owt \ && git remote add origin {{ GIT }}/desktop-app/tg_owt.git \ - && git fetch --depth=1 origin afd9d5d31798d3eacf9ed6c30601e91d0f1e4d60 \ + && git fetch --depth=1 origin c9cc4390ab951f2cbc103ff783a11f398b27660b \ && git reset --hard FETCH_HEAD \ && git submodule update --init --recursive --depth=1 \ && rm -rf .git \ diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index a571c7197..f1a80eb02 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -165,7 +165,7 @@ parts: patches: source: https://github.com/desktop-app/patches.git source-depth: 1 - source-commit: 803f1c2630f5eb0d3b00ba3f095b3079c0533156 + source-commit: 8639205c2095a3f015449f8995fbfe16405218b8 plugin: dump override-pull: | craftctl default @@ -434,7 +434,7 @@ parts: webrtc: source: https://github.com/desktop-app/tg_owt.git source-depth: 1 - source-commit: 3bb3d757681e6cc5135aec6529a753dc3dcdcfb9 + source-commit: c9cc4390ab951f2cbc103ff783a11f398b27660b plugin: cmake build-environment: - LDFLAGS: ${LDFLAGS:+$LDFLAGS} -s From 4e9b5b0d33a1dddd0100dc0b4a2bd2e613fe0ab6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 15 Jun 2024 22:45:21 +0400 Subject: [PATCH 014/142] Beta version 5.1.8: Fix build. --- Telegram/SourceFiles/settings/settings_experimental.cpp | 2 +- Telegram/lib_ui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_experimental.cpp b/Telegram/SourceFiles/settings/settings_experimental.cpp index 014987245..34a97dfad 100644 --- a/Telegram/SourceFiles/settings/settings_experimental.cpp +++ b/Telegram/SourceFiles/settings/settings_experimental.cpp @@ -145,7 +145,7 @@ void SetupExperimental( addToggle(Core::kOptionFractionalScalingEnabled); addToggle(Window::kOptionViewProfileInChatsListContextMenu); addToggle(Info::Profile::kOptionShowPeerIdBelowAbout); - addToggle(Ui::GL::kOptionAllowLinuxNvidiaOpenGL); + addToggle(Ui::GL::kOptionAllowX11NvidiaOpenGL); addToggle(Ui::kOptionUseSmallMsgBubbleRadius); addToggle(Media::Player::kOptionDisableAutoplayNext); addToggle(kOptionSendLargePhotos); diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 179a7ec1c..83eae8c9e 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 179a7ec1c1c4310e1c8c935e466d23cbfbe07221 +Subproject commit 83eae8c9e03bedb791ef47034079a3bd265e17da From 81d7fcba7e3e6241755fecc1a648f63961a8f9bf Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 15 Jun 2024 23:03:55 +0400 Subject: [PATCH 015/142] Beta version 5.1.8: Fix build with GCC. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 83eae8c9e..e54296fc1 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 83eae8c9e03bedb791ef47034079a3bd265e17da +Subproject commit e54296fc1d8a8b1a3d92846ce7d02f3f90b0a32e From 032fe3e0fc16421642418717b2a2b0b2032bc22b Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sat, 15 Jun 2024 22:40:04 +0400 Subject: [PATCH 016/142] Remove std::optional indrection for nullable XCB types --- .../SourceFiles/platform/linux/main_window_linux.cpp | 12 ++++++------ Telegram/lib_base | 2 +- Telegram/lib_ui | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index ff9fddc74..764b90bb6 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -57,7 +57,7 @@ void XCBSkipTaskbar(QWindow *window, bool skip) { } const auto root = base::Platform::XCB::GetRootWindow(connection); - if (!root.has_value()) { + if (!root) { return; } @@ -65,7 +65,7 @@ void XCBSkipTaskbar(QWindow *window, bool skip) { connection, "_NET_WM_STATE"); - if (!stateAtom.has_value()) { + if (!stateAtom) { return; } @@ -73,18 +73,18 @@ void XCBSkipTaskbar(QWindow *window, bool skip) { connection, "_NET_WM_STATE_SKIP_TASKBAR"); - if (!skipTaskbarAtom.has_value()) { + if (!skipTaskbarAtom) { return; } xcb_client_message_event_t xev; xev.response_type = XCB_CLIENT_MESSAGE; - xev.type = *stateAtom; + xev.type = stateAtom; xev.sequence = 0; xev.window = window->winId(); xev.format = 32; xev.data.data32[0] = skip ? 1 : 0; - xev.data.data32[1] = *skipTaskbarAtom; + xev.data.data32[1] = skipTaskbarAtom; xev.data.data32[2] = 0; xev.data.data32[3] = 0; xev.data.data32[4] = 0; @@ -92,7 +92,7 @@ void XCBSkipTaskbar(QWindow *window, bool skip) { xcb_send_event( connection, false, - *root, + root, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, reinterpret_cast(&xev)); diff --git a/Telegram/lib_base b/Telegram/lib_base index 4d56f8b4b..e14c5543f 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 4d56f8b4bba52b46844f46fafa5f9b6b2704429e +Subproject commit e14c5543f08221d261d8cd015219be5c1062e080 diff --git a/Telegram/lib_ui b/Telegram/lib_ui index e54296fc1..3ac659ec2 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit e54296fc1d8a8b1a3d92846ce7d02f3f90b0a32e +Subproject commit 3ac659ec2634974308254fe7123e31c36ebf662b From 5810149a77c6ffb95244a6b6fe8fb04b164607ae Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 16 Jun 2024 10:27:00 +0400 Subject: [PATCH 017/142] Improve chats search loading indication. --- .../dialogs/dialogs_inner_widget.cpp | 28 ++++++++++++---- .../dialogs/dialogs_inner_widget.h | 14 ++++++-- .../SourceFiles/dialogs/dialogs_widget.cpp | 32 ++++++++++++------- Telegram/SourceFiles/dialogs/dialogs_widget.h | 7 ++-- 4 files changed, 57 insertions(+), 24 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index b18471d0e..e10544c6d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -2588,6 +2588,15 @@ void InnerWidget::dragPinnedFromTouch() { updateReorderPinned(now); } +void InnerWidget::searchRequested(bool loading) { + _searchWaiting = false; + _searchLoading = loading; + if (loading) { + clearSearchResults(true); + } + refresh(true); +} + void InnerWidget::applySearchState(SearchState state) { if (_searchState == state) { return; @@ -2700,9 +2709,13 @@ void InnerWidget::applySearchState(SearchState state) { clearMouseSelection(true); } if (_state != WidgetState::Default) { - _searchLoading = true; - _searchMessages.fire({}); - refresh(true); + _searchWaiting = true; + _searchRequests.fire(otherChanged + ? SearchRequestDelay::Instant + : SearchRequestDelay::Delayed); + if (_searchWaiting) { + refresh(true); + } } } @@ -2918,8 +2931,8 @@ rpl::producer InnerWidget::dialogMoved() const { return _dialogMoved.events(); } -rpl::producer<> InnerWidget::searchMessages() const { - return _searchMessages.events(); +rpl::producer InnerWidget::searchRequests() const { + return _searchRequests.events(); } rpl::producer InnerWidget::completeHashtagRequests() const { @@ -3007,6 +3020,7 @@ void InnerWidget::searchReceived( HistoryItem *inject, SearchRequestType type, int fullCount) { + _searchWaiting = false; _searchLoading = false; const auto uniquePeers = uniqueSearchResults(); @@ -3171,7 +3185,7 @@ void InnerWidget::refreshEmpty() { && _searchResults.empty() && _peerSearchResults.empty() && _hashtagResults.empty(); - if (_searchLoading || !empty) { + if (_searchLoading || _searchWaiting || !empty) { if (_searchEmpty) { _searchEmpty->hide(); } @@ -3185,7 +3199,7 @@ void InnerWidget::refreshEmpty() { _searchEmpty->show(); } - if (!_searchLoading || !empty) { + if ((!_searchLoading && !_searchWaiting) || !empty) { _loadingAnimation.destroy(); } else if (!_loadingAnimation) { _loadingAnimation = Ui::CreateLoadingDialogRowWidget( diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index ebb22aa82..3adad0cf4 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -71,7 +71,7 @@ struct ChosenRow { bool newWindow : 1 = false; }; -enum class SearchRequestType { +enum class SearchRequestType : uchar { FromStart, FromOffset, PeerFromStart, @@ -80,6 +80,12 @@ enum class SearchRequestType { MigratedFromOffset, }; +enum class SearchRequestDelay : uchar { + InCache, + Instant, + Delayed, +}; + enum class WidgetState { Default, Filtered, @@ -145,6 +151,7 @@ public: } [[nodiscard]] bool hasFilteredResults() const; + void searchRequested(bool loading); void applySearchState(SearchState state); [[nodiscard]] auto searchTagsChanges() const -> rpl::producer>; @@ -168,7 +175,7 @@ public: [[nodiscard]] rpl::producer scrollByDeltaRequests() const; [[nodiscard]] rpl::producer mustScrollTo() const; [[nodiscard]] rpl::producer dialogMoved() const; - [[nodiscard]] rpl::producer<> searchMessages() const; + [[nodiscard]] rpl::producer searchRequests() const; [[nodiscard]] rpl::producer completeHashtagRequests() const; [[nodiscard]] rpl::producer<> refreshHashtagsRequests() const; @@ -529,7 +536,7 @@ private: rpl::event_stream _mustScrollTo; rpl::event_stream _dialogMoved; - rpl::event_stream<> _searchMessages; + rpl::event_stream _searchRequests; rpl::event_stream _completeHashtagRequests; rpl::event_stream<> _refreshHashtagsRequests; @@ -547,6 +554,7 @@ private: bool _savedSublists = false; bool _searchLoading = false; + bool _searchWaiting = false; base::unique_qptr _menu; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 068c30815..61689d105 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -95,6 +95,7 @@ namespace { constexpr auto kSearchPerPage = 50; constexpr auto kStoriesExpandDuration = crl::time(200); +constexpr auto kSearchRequestDelay = crl::time(900); base::options::toggle OptionForumHideChatsList({ .id = kOptionForumHideChatsList, @@ -324,9 +325,9 @@ Widget::Widget( _scroll->scrollToY(st + _inner->st()->height); } }, lifetime()); - _inner->searchMessages( - ) | rpl::start_with_next([=] { - searchRequested(); + _inner->searchRequests( + ) | rpl::start_with_next([=](SearchRequestDelay delay) { + searchRequested(delay); }, lifetime()); _inner->completeHashtagRequests( ) | rpl::start_with_next([=](const QString &tag) { @@ -1921,7 +1922,7 @@ void Widget::loadMoreBlockedByDate() { session().api().requestMoreBlockedByDateDialogs(); } -bool Widget::search(bool inCache) { +bool Widget::search(bool inCache, SearchRequestDelay delay) { _processingSearch = true; const auto guard = gsl::finally([&] { _processingSearch = false; @@ -1950,7 +1951,7 @@ bool Widget::search(bool inCache) { return true; } else if (inCache) { const auto success = _singleMessageSearch.lookup(query, [=] { - searchRequested(); + searchRequested(delay); }); if (!success) { return false; @@ -2068,6 +2069,9 @@ bool Widget::search(bool inCache) { }).send(); _searchQueries.emplace(_searchRequest, _searchQuery); } + _inner->searchRequested(true); + } else { + _inner->searchRequested(false); } const auto peerQuery = Api::ConvertPeerSearchQuery(query); if (searchForPeersRequired(peerQuery)) { @@ -2130,9 +2134,14 @@ bool Widget::searchForTopicsRequired(const QString &query) const { && !_openedForum->topicsList()->loaded(); } -void Widget::searchRequested() { - if (!search(true)) { - _searchTimer.callOnce(AutoSearchTimeout); +void Widget::searchRequested(SearchRequestDelay delay) { + if (search(true, delay)) { + return; + } else if (delay == SearchRequestDelay::Instant) { + _searchTimer.cancel(); + search(); + } else { + _searchTimer.callOnce(kSearchRequestDelay); } } @@ -2187,10 +2196,11 @@ void Widget::searchTopics() { } void Widget::searchMore() { - if (_searchRequest || _searchInHistoryRequest) { + if (_searchRequest + || _searchInHistoryRequest + || _searchTimer.isActive()) { return; - } - if (!_searchFull) { + } else if (!_searchFull) { if (const auto peer = searchInPeer()) { auto &histories = session().data().histories(); const auto topic = searchInTopic(); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 581bfdc00..46e5b55e0 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -74,7 +74,8 @@ class FakeRow; class Key; struct ChosenRow; class InnerWidget; -enum class SearchRequestType; +enum class SearchRequestType : uchar; +enum class SearchRequestDelay : uchar; class Suggestions; class ChatSearchIn; enum class ChatSearchTab : uchar; @@ -156,8 +157,8 @@ private: [[nodiscard]] QString currentSearchQuery() const; [[nodiscard]] int currentSearchQueryCursorPosition() const; void clearSearchField(); - void searchRequested(); - bool search(bool inCache = false); + void searchRequested(SearchRequestDelay delay); + bool search(bool inCache = false, SearchRequestDelay after = {}); void searchTopics(); void searchMore(); From 3d114131e0d32fb0efed4f3d0d5069bc3928db5b Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 16 Jun 2024 10:27:14 +0400 Subject: [PATCH 018/142] Fix premium gifting recipient choosing. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/boxes/gift_premium_box.cpp | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 7a3c42b06..7d7ff8f2d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -897,6 +897,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_gift_premium_users_confirm" = "Proceed"; "lng_settings_gift_premium_users_error#one" = "You can select maximum {count} user."; "lng_settings_gift_premium_users_error#other" = "You can select maximum {count} users."; +"lng_settings_gift_premium_choose" = "Please choose at least one recipient."; "lng_backgrounds_header" = "Choose Wallpaper"; "lng_theme_sure_keep" = "Keep this theme?"; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index ef87c6423..251b94b75 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -1011,14 +1011,16 @@ void GiftPremiumValidator::showChoosePeerBox(const QString &ref) { }) | ranges::views::filter([](UserData *u) -> bool { return u; }) | ranges::to>>(); - if (!users.empty()) { - const auto giftBox = show->show( - Box(GiftsBox, _controller, users, api, ref)); - giftBox->boxClosing( - ) | rpl::start_with_next([=] { - _manyGiftsLifetime.destroy(); - }, giftBox->lifetime()); + if (users.empty()) { + show->showToast( + tr::lng_settings_gift_premium_choose(tr::now)); } + const auto giftBox = show->show( + Box(GiftsBox, _controller, users, api, ref)); + giftBox->boxClosing( + ) | rpl::start_with_next([=] { + _manyGiftsLifetime.destroy(); + }, giftBox->lifetime()); (*ignoreClose) = true; peersBox->closeBox(); }; From 4047f1733decd5edf96d125589f128758b68d922 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 16 Jun 2024 12:16:36 +0400 Subject: [PATCH 019/142] Update submodules --- Telegram/ThirdParty/dispatch | 2 +- Telegram/ThirdParty/fcitx5-qt | 2 +- Telegram/ThirdParty/kcoreaddons | 2 +- Telegram/ThirdParty/kimageformats | 2 +- Telegram/ThirdParty/nimf | 2 +- Telegram/ThirdParty/xdg-desktop-portal | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Telegram/ThirdParty/dispatch b/Telegram/ThirdParty/dispatch index ee39300b1..542b7f323 160000 --- a/Telegram/ThirdParty/dispatch +++ b/Telegram/ThirdParty/dispatch @@ -1 +1 @@ -Subproject commit ee39300b12a77efd3f2f020e009e42d557adbb29 +Subproject commit 542b7f32311680b11b6fc8fcb2576955460ba7da diff --git a/Telegram/ThirdParty/fcitx5-qt b/Telegram/ThirdParty/fcitx5-qt index cc77e32c0..c743b12e6 160000 --- a/Telegram/ThirdParty/fcitx5-qt +++ b/Telegram/ThirdParty/fcitx5-qt @@ -1 +1 @@ -Subproject commit cc77e32c0ab675a663a7c019b3bb8cfcc60c5ec3 +Subproject commit c743b12e6780edf1dcfe9071531c80f050cacb95 diff --git a/Telegram/ThirdParty/kcoreaddons b/Telegram/ThirdParty/kcoreaddons index 79b99f162..fd84da51b 160000 --- a/Telegram/ThirdParty/kcoreaddons +++ b/Telegram/ThirdParty/kcoreaddons @@ -1 +1 @@ -Subproject commit 79b99f162b200413671dbabe21c73356d9956e35 +Subproject commit fd84da51b554eac25e35b1e3f373edaab3029b15 diff --git a/Telegram/ThirdParty/kimageformats b/Telegram/ThirdParty/kimageformats index 63a9de758..106279d32 160000 --- a/Telegram/ThirdParty/kimageformats +++ b/Telegram/ThirdParty/kimageformats @@ -1 +1 @@ -Subproject commit 63a9de758f4132b73ea4535fd9dd7fde3138dc33 +Subproject commit 106279d32ec4b93ccf5e29a92616e0f0cc8d2382 diff --git a/Telegram/ThirdParty/nimf b/Telegram/ThirdParty/nimf index 955ee48b4..498ec7ffa 160000 --- a/Telegram/ThirdParty/nimf +++ b/Telegram/ThirdParty/nimf @@ -1 +1 @@ -Subproject commit 955ee48b4f4020b317ece1e3b1e58c0b7f4bd0f3 +Subproject commit 498ec7ffab3ac140c2469638a14451788f03e798 diff --git a/Telegram/ThirdParty/xdg-desktop-portal b/Telegram/ThirdParty/xdg-desktop-portal index fa8d41a2f..11c8a96b1 160000 --- a/Telegram/ThirdParty/xdg-desktop-portal +++ b/Telegram/ThirdParty/xdg-desktop-portal @@ -1 +1 @@ -Subproject commit fa8d41a2f9a5d30a1e41568b6fb53b046dce14dc +Subproject commit 11c8a96b147aeae70e3f770313f93b367d53fedd From b9677fe1db7bcc5061336068d677daa202f57c34 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 18 Jun 2024 21:34:36 +0400 Subject: [PATCH 020/142] Update Qt to 6.7.2 on Linux --- Telegram/build/docker/centos_env/Dockerfile | 4 ++-- snap/snapcraft.yaml | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 70f19b2c6..2b0c82ed0 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -1,6 +1,6 @@ {%- set GIT = "https://github.com" -%} {%- set GIT_FREEDESKTOP = GIT ~ "/gitlab-freedesktop-mirrors" -%} -{%- set QT = "6.7.1" -%} +{%- set QT = "6.7.2" -%} {%- set QT_TAG = "v" ~ QT -%} {%- set CMAKE_VER = "3.27.6" -%} {%- set CMAKE_FILE = "cmake-" ~ CMAKE_VER ~ "-Linux-x86_64.sh" -%} @@ -51,7 +51,7 @@ FROM builder AS patches RUN git init patches \ && cd patches \ && git remote add origin {{ GIT }}/desktop-app/patches.git \ - && git fetch --depth=1 origin 8639205c2095a3f015449f8995fbfe16405218b8 \ + && git fetch --depth=1 origin 20a7c5ffd8265fc6e45203ea2536f7b1965be19a \ && git reset --hard FETCH_HEAD \ && rm -rf .git diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index f1a80eb02..454da33b1 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -165,7 +165,7 @@ parts: patches: source: https://github.com/desktop-app/patches.git source-depth: 1 - source-commit: 8639205c2095a3f015449f8995fbfe16405218b8 + source-commit: 20a7c5ffd8265fc6e45203ea2536f7b1965be19a plugin: dump override-pull: | craftctl default @@ -360,7 +360,7 @@ parts: - mesa-vulkan-drivers - xkb-data override-pull: | - QT=6.7.1 + QT=6.7.2 git clone -b v${QT} --depth=1 https://github.com/qt/qt5.git . git submodule update --init --recursive --depth=1 qtbase qtdeclarative qtwayland qtimageformats qtsvg qtshadertools @@ -369,7 +369,6 @@ parts: find $CRAFT_STAGE/patches/qtbase_${QT} -type f -print0 | sort -z | xargs -r0 git apply cd ../qtwayland find $CRAFT_STAGE/patches/qtwayland_${QT} -type f -print0 | sort -z | xargs -r0 git apply - sed -i 's/qMin(version, 8)/qMin(version, 7)/' src/client/qwaylandinputdevice.cpp cd .. override-build: | ./configure \ From eaf4575eb85e446d10f3afc4f9fe67e7bf4756c7 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 18 Jun 2024 02:45:45 +0400 Subject: [PATCH 021/142] Fix IV window geometry restoration --- Telegram/SourceFiles/iv/iv_delegate.h | 6 +++++- Telegram/SourceFiles/iv/iv_delegate_impl.cpp | 4 ++-- Telegram/SourceFiles/iv/iv_delegate_impl.h | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/iv/iv_delegate.h b/Telegram/SourceFiles/iv/iv_delegate.h index fd94cac0a..09374fa17 100644 --- a/Telegram/SourceFiles/iv/iv_delegate.h +++ b/Telegram/SourceFiles/iv/iv_delegate.h @@ -7,13 +7,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +namespace Ui { +class RpWindow; +} // namespace Ui + namespace Iv { class Delegate { public: virtual void ivSetLastSourceWindow(not_null window) = 0; [[nodiscard]] virtual QRect ivGeometry() const = 0; - virtual void ivSaveGeometry(not_null window) = 0; + virtual void ivSaveGeometry(not_null window) = 0; }; } // namespace Iv diff --git a/Telegram/SourceFiles/iv/iv_delegate_impl.cpp b/Telegram/SourceFiles/iv/iv_delegate_impl.cpp index 5ac753d17..4de0fa03c 100644 --- a/Telegram/SourceFiles/iv/iv_delegate_impl.cpp +++ b/Telegram/SourceFiles/iv/iv_delegate_impl.cpp @@ -67,7 +67,7 @@ QRect DelegateImpl::ivGeometry() const { return result; } -void DelegateImpl::ivSaveGeometry(not_null window) { +void DelegateImpl::ivSaveGeometry(not_null window) { if (!window->windowHandle()) { return; } @@ -82,7 +82,7 @@ void DelegateImpl::ivSaveGeometry(not_null window) { realPosition.moncrc = 0; DEBUG_LOG(("IV Pos: Saving maximized position.")); } else { - auto r = window->geometry(); + auto r = window->body()->mapToGlobal(window->body()->rect()); realPosition.x = r.x(); realPosition.y = r.y(); realPosition.w = r.width(); diff --git a/Telegram/SourceFiles/iv/iv_delegate_impl.h b/Telegram/SourceFiles/iv/iv_delegate_impl.h index 32b15ae8f..9c7c0fb9d 100644 --- a/Telegram/SourceFiles/iv/iv_delegate_impl.h +++ b/Telegram/SourceFiles/iv/iv_delegate_impl.h @@ -17,7 +17,7 @@ public: void ivSetLastSourceWindow(not_null window) override; [[nodiscard]] QRect ivGeometry() const override; - void ivSaveGeometry(not_null window) override; + void ivSaveGeometry(not_null window) override; private: QPointer _lastSourceWindow; From 750ad600befece7c870b6ad7ac2976c2ad98ac0d Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 18 Jun 2024 03:01:46 +0400 Subject: [PATCH 022/142] Store media viewer geometry the same way as main window --- .../SourceFiles/media/view/media_view_overlay_widget.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index ecbb7b06f..6430bd56f 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -867,7 +867,8 @@ void OverlayWidget::savePosition() { } else if (!_wasWindowedMode && !Platform::IsMac()) { return; } else { - auto r = _normalGeometry = _window->geometry(); + auto r = _normalGeometry = _window->body()->mapToGlobal( + _window->body()->rect()); realPosition.x = r.x(); realPosition.y = r.y(); realPosition.w = r.width(); @@ -912,7 +913,7 @@ void OverlayWidget::updateGeometry(bool inMove) { .arg(_normalGeometry.y()) .arg(_normalGeometry.width()) .arg(_normalGeometry.height())); - _window->RpWidget::setGeometry(_normalGeometry); + _window->setGeometry(_normalGeometry); } if constexpr (!Platform::IsMac()) { if (_fullscreen) { From 8a5797e1bd2c0e4ae49a6722d3b5231ef787288c Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 19 Jun 2024 02:47:37 +0400 Subject: [PATCH 023/142] Re-create QSystemTrayIcon on SNI watcher owner change --- .../SourceFiles/platform/linux/tray_linux.cpp | 25 ++++++++++++++++++- .../SourceFiles/platform/linux/tray_linux.h | 5 ++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/platform/linux/tray_linux.cpp b/Telegram/SourceFiles/platform/linux/tray_linux.cpp index ab1e34df8..2d0e23aec 100644 --- a/Telegram/SourceFiles/platform/linux/tray_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/tray_linux.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/invoke_queued.h" #include "base/qt_signal_producer.h" +#include "base/platform/linux/base_linux_dbus_utilities.h" #include "core/application.h" #include "core/sandbox.h" #include "platform/platform_specific.h" @@ -21,9 +22,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include + namespace Platform { namespace { +using namespace gi::repository; + [[nodiscard]] QString PanelIconName(int counter, bool muted) { return (counter > 0) ? (muted @@ -282,11 +287,29 @@ rpl::producer<> TrayEventFilter::contextMenuFilters() const { } Tray::Tray() { - LOG(("System tray available: %1").arg(Logs::b(TrayIconSupported()))); + auto connection = Gio::bus_get_sync(Gio::BusType::SESSION_, nullptr); + if (connection) { + _sniWatcher = std::make_unique( + connection.gobj_(), + "org.kde.StatusNotifierWatcher", + [=]( + const std::string &service, + const std::string &oldOwner, + const std::string &newOwner) { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + if (hasIcon()) { + destroyIcon(); + createIcon(); + } + }); + }); + } } void Tray::createIcon() { if (!_icon) { + LOG(("System tray available: %1").arg(Logs::b(TrayIconSupported()))); + if (!_iconGraphic) { _iconGraphic = std::make_unique(); } diff --git a/Telegram/SourceFiles/platform/linux/tray_linux.h b/Telegram/SourceFiles/platform/linux/tray_linux.h index dd0175a2c..712592777 100644 --- a/Telegram/SourceFiles/platform/linux/tray_linux.h +++ b/Telegram/SourceFiles/platform/linux/tray_linux.h @@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unique_qptr.h" +namespace base::Platform::DBus { +class ServiceWatcher; +} // namespace base::Platform::DBus + namespace Ui { class PopupMenu; } // namespace Ui @@ -51,6 +55,7 @@ public: [[nodiscard]] rpl::lifetime &lifetime(); private: + std::unique_ptr _sniWatcher; std::unique_ptr _iconGraphic; base::unique_qptr _icon; From a22cf8e3035ac5da79c4c94faea29e65aae4eeb0 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 18 Jun 2024 12:32:27 +0400 Subject: [PATCH 024/142] Use RpWidget::windowActiveValue --- .../SourceFiles/dialogs/dialogs_widget.cpp | 2 +- .../dialogs/ui/dialogs_stories_list.cpp | 16 +++------------- .../dialogs/ui/dialogs_stories_list.h | 2 +- Telegram/SourceFiles/iv/iv_controller.cpp | 8 +++----- Telegram/SourceFiles/mainwindow.cpp | 6 ++++-- .../media/stories/media_stories_controller.cpp | 11 +++-------- Telegram/SourceFiles/window/main_window.cpp | 18 +++++++++--------- Telegram/SourceFiles/window/main_window.h | 2 +- 8 files changed, 25 insertions(+), 40 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 61689d105..7caeb6117 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -897,7 +897,7 @@ void Widget::setupStories() { Core::App().saveSettingsDelayed(); }; _stories->setShowTooltip( - parentWidget(), + controller()->content(), rpl::combine( Core::App().settings().storiesClickTooltipHiddenValue(), shownValue(), diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp index 1e4a4cd5d..7bca7a120 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp @@ -903,7 +903,7 @@ TextWithEntities List::computeTooltipText() const { } void List::setShowTooltip( - not_null tooltipParent, + not_null tooltipParent, rpl::producer shown, Fn hide) { _tooltip = nullptr; @@ -925,16 +925,6 @@ void List::setShowTooltip( tooltip->toggleFast(false); updateTooltipGeometry(); - const auto handle = tooltipParent->window()->windowHandle(); - auto windowActive = rpl::single( - handle->isActive() - ) | rpl::then(base::qt_signal_producer( - handle, - &QWindow::activeChanged - ) | rpl::map([=] { - return handle->isActive(); - })) | rpl::distinct_until_changed(); - { const auto recompute = [=] { updateTooltipGeometry(); @@ -955,7 +945,7 @@ void List::setShowTooltip( _tooltipText.value() | rpl::map( notEmpty ) | rpl::distinct_until_changed(), - std::move(windowActive) + tooltipParent->windowActiveValue() ) | rpl::start_with_next([=](bool, bool, bool active) { _tooltipWindowActive = active; if (!isHidden()) { @@ -981,7 +971,7 @@ void List::toggleTooltip(bool fast) { && !isHidden() && _tooltipNotHidden.current() && !_tooltipText.current().empty() - && window()->windowHandle()->isActive(); + && isActiveWindow(); if (_tooltip) { if (fast) { _tooltip->toggleFast(shown); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h index 303938957..c3140a9ca 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h @@ -72,7 +72,7 @@ public: style::align alignSmall, QRect geometryFull = QRect()); void setShowTooltip( - not_null tooltipParent, + not_null tooltipParent, rpl::producer shown, Fn hide); void raiseTooltip(); diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index 3b30dc6db..baad49dd1 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -342,11 +342,9 @@ void Controller::createWindow() { _window = std::make_unique(); const auto window = _window.get(); - base::qt_signal_producer( - window->window()->windowHandle(), - &QWindow::activeChanged - ) | rpl::filter([=] { - return _webview && window->window()->windowHandle()->isActive(); + window->windowActiveValue( + ) | rpl::filter([=](bool active) { + return _webview && active; }) | rpl::start_with_next([=] { setInnerFocus(); }, window->lifetime()); diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index 93d6d3060..2e705713f 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -139,7 +139,9 @@ void MainWindow::finishFirstShow() { applyInitialWorkMode(); createGlobalMenu(); - windowDeactivateEvents( + windowActiveValue( + ) | rpl::skip(1) | rpl::filter( + !rpl::mappers::_1 ) | rpl::start_with_next([=] { Ui::Tooltip::Hide(); }, lifetime()); @@ -594,7 +596,7 @@ bool MainWindow::eventFilter(QObject *object, QEvent *e) { case QEvent::ApplicationActivate: { if (object == QCoreApplication::instance()) { InvokeQueued(this, [=] { - handleActiveChanged(); + handleActiveChanged(isActiveWindow()); }); } } break; diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 04c06e197..45e17f33f 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -328,16 +328,11 @@ Controller::Controller(not_null delegate) } }, _lifetime); - const auto window = _wrap->window()->windowHandle(); - Assert(window != nullptr); - base::qt_signal_producer( - window, - &QWindow::activeChanged - ) | rpl::start_with_next([=] { - _windowActive = window->isActive(); + _wrap->windowActiveValue( + ) | rpl::start_with_next([=](bool active) { + _windowActive = active; updatePlayingAllowed(); }, _lifetime); - _windowActive = window->isActive(); _contentFadeAnimation.stop(); } diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index e776b7637..a8b6cf861 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -351,6 +351,13 @@ MainWindow::MainWindow(not_null controller) Ui::Toast::SetDefaultParent(_body.data()); } + windowActiveValue( + ) | rpl::skip(1) | rpl::start_with_next([=](bool active) { + InvokeQueued(this, [=] { + handleActiveChanged(active); + }); + }, lifetime()); + body()->sizeValue( ) | rpl::start_with_next([=](QSize size) { updateControlsGeometry(); @@ -445,13 +452,6 @@ void MainWindow::init() { initHook(); - // Non-queued activeChanged handlers must use QtSignalProducer. - connect( - windowHandle(), - &QWindow::activeChanged, - this, - [=] { handleActiveChanged(); }, - Qt::QueuedConnection); connect( windowHandle(), &QWindow::windowStateChanged, @@ -495,9 +495,9 @@ void MainWindow::handleStateChanged(Qt::WindowState state) { savePosition(state); } -void MainWindow::handleActiveChanged() { +void MainWindow::handleActiveChanged(bool active) { checkActivation(); - if (isActiveWindow()) { + if (active) { Core::App().windowActivated(&controller()); } if (const auto controller = sessionController()) { diff --git a/Telegram/SourceFiles/window/main_window.h b/Telegram/SourceFiles/window/main_window.h index 7bbbf8e86..49f73e6bb 100644 --- a/Telegram/SourceFiles/window/main_window.h +++ b/Telegram/SourceFiles/window/main_window.h @@ -151,7 +151,7 @@ protected: void savePosition(Qt::WindowState state = Qt::WindowActive); void handleStateChanged(Qt::WindowState state); - void handleActiveChanged(); + void handleActiveChanged(bool active); void handleVisibleChanged(bool visible); virtual void checkActivation() { From f768e405faabd5c2048bcf4021db2b1c49601f31 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 18 Jun 2024 13:11:35 +0400 Subject: [PATCH 025/142] Use RpWidget::shownValue instead of QWindow::visibleChanged --- Telegram/SourceFiles/history/history_widget.cpp | 9 ++++----- Telegram/SourceFiles/window/main_window.cpp | 12 +++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index e81fc1bc2..b19c0222f 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -378,11 +378,10 @@ HistoryWidget::HistoryWidget( _field->setEnabled(shown); }, _field->lifetime()); #endif // Q_OS_MAC - connect( - controller->widget()->windowHandle(), - &QWindow::visibleChanged, - this, - [=] { windowIsVisibleChanged(); }); + controller->widget()->shownValue( + ) | rpl::skip(1) | rpl::start_with_next([=] { + windowIsVisibleChanged(); + }, lifetime()); initTabbedSelector(); diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index a8b6cf861..77ce6d20f 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -358,6 +358,13 @@ MainWindow::MainWindow(not_null controller) }); }, lifetime()); + shownValue( + ) | rpl::skip(1) | rpl::start_with_next([=](bool visible) { + InvokeQueued(this, [=] { + handleVisibleChanged(visible); + }); + }, lifetime()); + body()->sizeValue( ) | rpl::start_with_next([=](QSize size) { updateControlsGeometry(); @@ -457,11 +464,6 @@ void MainWindow::init() { &QWindow::windowStateChanged, this, [=](Qt::WindowState state) { handleStateChanged(state); }); - connect( - windowHandle(), - &QWindow::visibleChanged, - this, - [=](bool visible) { handleVisibleChanged(visible); }); updatePalette(); From fc5e9414b7c731bc67f32d2a70440c627374299a Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 18 Jun 2024 13:01:17 +0400 Subject: [PATCH 026/142] Subscribe to QEvent::WindowStateChange instead of QWindow::windowStateChanged --- .../calls/group/calls_group_panel.cpp | 14 ++----- .../media/view/media_view_overlay_widget.cpp | 41 ++++++++----------- Telegram/SourceFiles/window/main_window.cpp | 6 --- 3 files changed, 21 insertions(+), 40 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 9e967b50e..84c336131 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -383,20 +383,14 @@ void Panel::initWindow() { && _fullScreenOrMaximized.current()) { toggleFullScreen(); } + } else if (e->type() == QEvent::WindowStateChange && _call->rtmp()) { + const auto state = window()->windowState(); + _fullScreenOrMaximized = (state & Qt::WindowFullScreen) + || (state & Qt::WindowMaximized); } return base::EventFilterResult::Continue; }); - if (_call->rtmp()) { - QObject::connect( - window()->windowHandle(), - &QWindow::windowStateChanged, - [=](Qt::WindowState state) { - _fullScreenOrMaximized = (state == Qt::WindowFullScreen) - || (state == Qt::WindowMaximized); - }); - } - window()->setBodyTitleArea([=](QPoint widgetPoint) { using Flag = Ui::WindowTitleHitTestFlag; const auto titleRect = QRect( diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 6430bd56f..7afc54199 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -504,6 +504,22 @@ OverlayWidget::OverlayWidget() if (handleContextMenu(position)) { return base::EventFilterResult::Cancel; } + } else if (e->type() == QEvent::WindowStateChange) { + const auto state = _window->windowState(); + if (state & Qt::WindowMinimized || Platform::IsMac()) { + } else if (state & Qt::WindowMaximized) { + if (_fullscreen || _windowed) { + _fullscreen = _windowed = false; + savePosition(); + } + } else if (_fullscreen || _windowed) { + } else if (state & Qt::WindowFullScreen) { + _fullscreen = true; + savePosition(); + } else { + _windowed = true; + savePosition(); + } } return base::EventFilterResult::Continue; }); @@ -733,29 +749,6 @@ void OverlayWidget::setupWindow() { return Flag::Move | Flag(0); }); - const auto callback = [=](Qt::WindowState state) { - if (state == Qt::WindowMinimized || Platform::IsMac()) { - return; - } else if (state == Qt::WindowMaximized) { - if (_fullscreen || _windowed) { - _fullscreen = _windowed = false; - savePosition(); - } - } else if (_fullscreen || _windowed) { - return; - } else if (state == Qt::WindowFullScreen) { - _fullscreen = true; - savePosition(); - } else { - _windowed = true; - savePosition(); - } - }; - QObject::connect( - _window->windowHandle(), - &QWindow::windowStateChanged, - callback); - _window->setAttribute(Qt::WA_NoSystemBackground, true); _window->setAttribute(Qt::WA_TranslucentBackground, true); @@ -3246,7 +3239,7 @@ bool OverlayWidget::isHidden() const { } bool OverlayWidget::isMinimized() const { - return _window->windowHandle()->windowState() == Qt::WindowMinimized; + return _window->isMinimized(); } bool OverlayWidget::isFullScreen() const { diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index 77ce6d20f..f1b1f7c0b 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -459,12 +459,6 @@ void MainWindow::init() { initHook(); - connect( - windowHandle(), - &QWindow::windowStateChanged, - this, - [=](Qt::WindowState state) { handleStateChanged(state); }); - updatePalette(); if (Ui::Platform::NativeWindowFrameSupported()) { From 60ca6895db28de18836cdd71a8638c4ce58b1f1b Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 19 Jun 2024 10:10:30 +0400 Subject: [PATCH 027/142] Update submodules --- Telegram/lib_base | 2 +- Telegram/lib_ui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/lib_base b/Telegram/lib_base index e14c5543f..953b0bf54 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit e14c5543f08221d261d8cd015219be5c1062e080 +Subproject commit 953b0bf548f36213414e6f1a65dd3ebad618350e diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 3ac659ec2..0dd59f1a9 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 3ac659ec2634974308254fe7123e31c36ebf662b +Subproject commit 0dd59f1a9c740d86083a80291a5081e3a9e3ecf8 From 053f8ad1c04c4c6d05223f68d5ad5629645e112d Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 21 Jun 2024 03:59:20 +0400 Subject: [PATCH 028/142] Ensure media viewer has native window handle before using it --- Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 7afc54199..9953b64da 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -630,6 +630,7 @@ OverlayWidget::OverlayWidget() _window->setWindowFlags(Qt::FramelessWindowHint | Qt::Tool); } _widget->setMouseTracking(true); + _window->createWinId(); QObject::connect( window(), From 93b7c47cda46d09f6ea2135d0d191e91addb0b54 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 21 Jun 2024 11:36:18 +0400 Subject: [PATCH 029/142] Support building on Windows with Qt 6. --- CMakeLists.txt | 8 -- Telegram/CMakeLists.txt | 19 ++- Telegram/SourceFiles/core/application.cpp | 6 +- Telegram/SourceFiles/core/sandbox.cpp | 2 +- Telegram/SourceFiles/core/sandbox.h | 3 +- .../media/view/media_view_overlay_widget.cpp | 1 + .../platform/win/file_utilities_win.cpp | 1 + .../platform/win/integration_win.cpp | 2 +- .../platform/win/integration_win.h | 2 +- .../platform/win/main_window_win.cpp | 7 +- .../platform/win/main_window_win.h | 2 +- .../SourceFiles/platform/win/specific_win.cpp | 1 - .../SourceFiles/platform/win/windows_dlls.cpp | 5 + .../settings/settings_advanced.cpp | 10 +- Telegram/build/prepare/prepare.py | 125 ++++++++++++++---- Telegram/build/qt_version.py | 17 +++ Telegram/cmake/generate_midl.cmake | 4 +- Telegram/cmake/td_scheme.cmake | 7 + Telegram/configure.py | 12 +- Telegram/lib_base | 2 +- Telegram/lib_ui | 2 +- cmake | 2 +- 22 files changed, 177 insertions(+), 63 deletions(-) create mode 100644 Telegram/build/qt_version.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e2ac34c2..9393d81d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,14 +57,6 @@ include(cmake/validate_d3d_compiler.cmake) include(cmake/target_prepare_qrc.cmake) include(cmake/options.cmake) - -if (NOT DESKTOP_APP_USE_PACKAGED) - if (WIN32) - set(qt_version 5.15.13) - elseif (APPLE) - set(qt_version 6.2.8) - endif() -endif() include(cmake/external/qt/package.cmake) set(desktop_app_skip_libs diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 1517a952e..20aa790eb 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1828,12 +1828,29 @@ if (WIN32) /DELAYLOAD:uxtheme.dll /DELAYLOAD:crypt32.dll /DELAYLOAD:bcrypt.dll - /DELAYLOAD:imm32.dll /DELAYLOAD:netapi32.dll /DELAYLOAD:userenv.dll /DELAYLOAD:wtsapi32.dll /DELAYLOAD:propsys.dll ) + if (QT_VERSION GREATER 6) + target_link_options(Telegram + PRIVATE + /DELAYLOAD:API-MS-Win-Core-Synch-l1-2-0.dll # Synchronization.lib + /DELAYLOAD:authz.dll # Authz.lib + /DELAYLOAD:dwrite.dll # DWrite.lib + /DELAYLOAD:dxgi.dll # DXGI.lib + /DELAYLOAD:d3d9.dll # D3D9.lib + /DELAYLOAD:d3d11.dll # D3D11.lib + /DELAYLOAD:d3d12.dll # D3D12.lib + /DELAYLOAD:setupapi.dll # SetupAPI.lib + ) + else() + target_link_options(Telegram + PRIVATE + /DELAYLOAD:imm32.dll + ) + endif() endif() target_prepare_qrc(Telegram) diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index d0470dfc7..8e16868c9 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -118,7 +118,7 @@ constexpr auto kFileOpenTimeoutMs = crl::time(1000); LaunchState GlobalLaunchState/* = LaunchState::Running*/; void SetCrashAnnotationsGL() { -#ifdef Q_OS_WIN +#ifdef DESKTOP_APP_USE_ANGLE CrashReports::SetAnnotation("OpenGL ANGLE", [] { if (Core::App().settings().disableOpenGL()) { return "Disabled"; @@ -131,11 +131,11 @@ void SetCrashAnnotationsGL() { } Unexpected("Ui::GL::CurrentANGLE value in SetupANGLE."); }()); -#else // Q_OS_WIN +#else // DESKTOP_APP_USE_ANGLE CrashReports::SetAnnotation( "OpenGL", Core::App().settings().disableOpenGL() ? "Disabled" : "Enabled"); -#endif // Q_OS_WIN +#endif // DESKTOP_APP_USE_ANGLE } } // namespace diff --git a/Telegram/SourceFiles/core/sandbox.cpp b/Telegram/SourceFiles/core/sandbox.cpp index 4110a73a7..41888829f 100644 --- a/Telegram/SourceFiles/core/sandbox.cpp +++ b/Telegram/SourceFiles/core/sandbox.cpp @@ -620,7 +620,7 @@ void Sandbox::processPostponedCalls(int level) { bool Sandbox::nativeEventFilter( const QByteArray &eventType, void *message, - base::NativeEventResult *result) { + native_event_filter_result *result) { registerEnterFromEventLoop(); return false; } diff --git a/Telegram/SourceFiles/core/sandbox.h b/Telegram/SourceFiles/core/sandbox.h index dfb1fe4a6..5dd5cea2b 100644 --- a/Telegram/SourceFiles/core/sandbox.h +++ b/Telegram/SourceFiles/core/sandbox.h @@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "mtproto/mtproto_proxy_data.h" -#include "base/qt/qt_common_adapters.h" #include #include @@ -87,7 +86,7 @@ private: bool nativeEventFilter( const QByteArray &eventType, void *message, - base::NativeEventResult *result) override; + native_event_filter_result *result) override; void processPostponedCalls(int level); void singleInstanceChecked(); void launchApplication(); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 9953b64da..065c91ebc 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "api/api_attached_stickers.h" #include "api/api_peer_photo.h" +#include "base/qt/qt_common_adapters.h" #include "lang/lang_keys.h" #include "boxes/premium_preview_box.h" #include "core/application.h" diff --git a/Telegram/SourceFiles/platform/win/file_utilities_win.cpp b/Telegram/SourceFiles/platform/win/file_utilities_win.cpp index a63249ea5..5bf883658 100644 --- a/Telegram/SourceFiles/platform/win/file_utilities_win.cpp +++ b/Telegram/SourceFiles/platform/win/file_utilities_win.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include +#include #include #include diff --git a/Telegram/SourceFiles/platform/win/integration_win.cpp b/Telegram/SourceFiles/platform/win/integration_win.cpp index 3d8b592e7..0a44470da 100644 --- a/Telegram/SourceFiles/platform/win/integration_win.cpp +++ b/Telegram/SourceFiles/platform/win/integration_win.cpp @@ -43,7 +43,7 @@ WindowsIntegration &WindowsIntegration::Instance() { bool WindowsIntegration::nativeEventFilter( const QByteArray &eventType, void *message, - long *result) { + native_event_filter_result *result) { return Core::Sandbox::Instance().customEnterFromEventLoop([&] { const auto msg = static_cast(message); return processEvent( diff --git a/Telegram/SourceFiles/platform/win/integration_win.h b/Telegram/SourceFiles/platform/win/integration_win.h index 0b29007d3..c58a2846c 100644 --- a/Telegram/SourceFiles/platform/win/integration_win.h +++ b/Telegram/SourceFiles/platform/win/integration_win.h @@ -29,7 +29,7 @@ private: bool nativeEventFilter( const QByteArray &eventType, void *message, - long *result) override; + native_event_filter_result *result) override; bool processEvent( HWND hWnd, UINT msg, diff --git a/Telegram/SourceFiles/platform/win/main_window_win.cpp b/Telegram/SourceFiles/platform/win/main_window_win.cpp index dd2f3dafe..ede58134f 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.cpp +++ b/Telegram/SourceFiles/platform/win/main_window_win.cpp @@ -30,7 +30,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_controller.h" #include "history/history.h" -#include #include #include #include @@ -81,7 +80,7 @@ private: bool nativeEventFilter( const QByteArray &eventType, void *message, - long *result) override; + native_event_filter_result *result) override; bool mainWindowEvent( HWND hWnd, @@ -172,7 +171,7 @@ EventFilter::EventFilter(not_null window) : _window(window) { bool EventFilter::nativeEventFilter( const QByteArray &eventType, void *message, - long *result) { + native_event_filter_result *result) { return Core::Sandbox::Instance().customEnterFromEventLoop([&] { const auto msg = static_cast(message); if (msg->hwnd == _window->psHwnd() @@ -483,7 +482,7 @@ bool MainWindow::initGeometryFromSystem() { bool MainWindow::nativeEvent( const QByteArray &eventType, void *message, - long *result) { + native_event_filter_result *result) { if (message) { const auto msg = static_cast(message); if (msg->message == WM_IME_STARTCOMPOSITION) { diff --git a/Telegram/SourceFiles/platform/win/main_window_win.h b/Telegram/SourceFiles/platform/win/main_window_win.h index 63dc6ba17..9f4884467 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.h +++ b/Telegram/SourceFiles/platform/win/main_window_win.h @@ -51,7 +51,7 @@ protected: bool nativeEvent( const QByteArray &eventType, void *message, - long *result) override; + native_event_filter_result *result) override; private: struct Private; diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index ef5c88e35..7a3b133c4 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -29,7 +29,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include -#include #include #include diff --git a/Telegram/SourceFiles/platform/win/windows_dlls.cpp b/Telegram/SourceFiles/platform/win/windows_dlls.cpp index 7184b2f4b..0e03904f7 100644 --- a/Telegram/SourceFiles/platform/win/windows_dlls.cpp +++ b/Telegram/SourceFiles/platform/win/windows_dlls.cpp @@ -8,13 +8,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/win/windows_dlls.h" #include "base/platform/win/base_windows_safe_library.h" +#include "ui/gl/gl_detection.h" #include #include #define LOAD_SYMBOL(lib, name) ::base::Platform::LoadMethod(lib, #name, name) +#ifdef DESKTOP_APP_USE_ANGLE bool DirectXResolveCompiler(); +#endif // DESKTOP_APP_USE_ANGLE namespace Platform { namespace Dlls { @@ -67,6 +70,7 @@ SafeIniter kSafeIniter; } // namespace void CheckLoadedModules() { +#ifdef DESKTOP_APP_USE_ANGLE if (DirectXResolveCompiler()) { auto LibD3DCompiler = HMODULE(); if (GetModuleHandleEx(0, L"d3dcompiler_47.dll", &LibD3DCompiler)) { @@ -88,6 +92,7 @@ void CheckLoadedModules() { } else { LOG(("Error: Could not resolve DirectX compiler library.")); } +#endif // DESKTOP_APP_USE_ANGLE } } // namespace Dlls diff --git a/Telegram/SourceFiles/settings/settings_advanced.cpp b/Telegram/SourceFiles/settings/settings_advanced.cpp index b833383f5..a2ae6487a 100644 --- a/Telegram/SourceFiles/settings/settings_advanced.cpp +++ b/Telegram/SourceFiles/settings/settings_advanced.cpp @@ -822,7 +822,7 @@ void SetupHardwareAcceleration(not_null container) { }, container->lifetime()); } -#ifdef Q_OS_WIN +#ifdef DESKTOP_APP_USE_ANGLE void SetupANGLE( not_null controller, not_null container) { @@ -895,7 +895,7 @@ void SetupANGLE( })); }); } -#endif // Q_OS_WIN +#endif // DESKTOP_APP_USE_ANGLE void SetupOpenGL( not_null controller, @@ -938,13 +938,13 @@ void SetupPerformance( not_null container) { SetupAnimations(&controller->window(), container); SetupHardwareAcceleration(container); -#ifdef Q_OS_WIN +#ifdef DESKTOP_APP_USE_ANGLE SetupANGLE(controller, container); -#else // Q_OS_WIN +#else // DESKTOP_APP_USE_ANGLE if constexpr (!Platform::IsMac()) { SetupOpenGL(controller, container); } -#endif // Q_OS_WIN +#endif // DESKTOP_APP_USE_ANGLE } void SetupWindowTitle( diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 011476c50..ccc7807dc 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1,7 +1,10 @@ import os, sys, pprint, re, json, pathlib, hashlib, subprocess, glob executePath = os.getcwd() +sys.dont_write_bytecode = True scriptPath = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(scriptPath + '/..') +import qt_version def finish(code): global executePath @@ -23,11 +26,24 @@ if win and not 'Platform' in os.environ: win32 = win and (os.environ['Platform'] == 'x86') win64 = win and (os.environ['Platform'] == 'x64') +winarm = win and (os.environ['Platform'] == 'arm') + +arch = '' +if win32: + arch = 'x86' +elif win64: + arch = 'x64' +elif winarm: + arch = 'arm' +if not qt_version.resolve(arch): + error('Usupported platform.') + +qt = os.environ.get('QT') if win and not 'COMSPEC' in os.environ: error('COMSPEC environment variable is not set.') -if win and not win32 and not win64: +if win and not win32 and not win64 and not winarm: nativeToolsError() os.chdir(scriptPath + '/../../../..') @@ -42,11 +58,8 @@ thirdPartyDir = os.path.realpath(os.path.join(rootDir, 'ThirdParty')) usedPrefix = os.path.realpath(os.path.join(libsDir, 'local')) optionsList = [ + 'qt6', 'skip-release', - 'build-qt5', - 'skip-qt5', - 'build-qt6', - 'skip-qt6', 'build-stackwalk', ] options = [] @@ -63,9 +76,6 @@ for arg in sys.argv[1:]: customRunCommand = True runCommand.append('shell') -buildQt5 = not 'skip-qt5' in options if win else 'build-qt5' in options -buildQt6 = 'build-qt6' in options if win else not 'skip-qt6' in options - if not os.path.isdir(os.path.join(libsDir, keysLoc)): pathlib.Path(os.path.join(libsDir, keysLoc)).mkdir(parents=True, exist_ok=True) if not os.path.isdir(os.path.join(thirdPartyDir, keysLoc)): @@ -435,7 +445,7 @@ if customRunCommand: stage('patches', """ git clone https://github.com/desktop-app/patches.git cd patches - git checkout 8639205c20 + git checkout 20a7c5ffd8 """) stage('msys64', """ @@ -1398,7 +1408,9 @@ release: lipo -create Release.arm64/libcrashpad_client.a Release.x86_64/libcrashpad_client.a -output Release/libcrashpad_client.a """) -stage('tg_angle', """ +if qt < '6': + if win: + stage('tg_angle', """ win: git clone https://github.com/desktop-app/tg_angle.git cd tg_angle @@ -1424,22 +1436,21 @@ release: cd ..\\..\\.. """) -if buildQt5: - stage('qt_5_15_13', """ - git clone -b v5.15.13-lts-lgpl https://github.com/qt/qt5.git qt_5_15_13 - cd qt_5_15_13 + stage('qt_' + qt, """ + git clone -b v$QT-lts-lgpl https://github.com/qt/qt5.git qt_$QT + cd qt_$QT git submodule update --init --recursive qtbase qtimageformats qtsvg -depends:patches/qtbase_5.15.13/*.patch +depends:patches/qtbase_""" + qt + """/*.patch cd qtbase win: - for /r %%i in (..\\..\\patches\\qtbase_5.15.13\\*) do git apply %%i -v + for /r %%i in (..\\..\\patches\\qtbase_%QT%\\*) do git apply %%i -v cd .. SET CONFIGURATIONS=-debug release: SET CONFIGURATIONS=-debug-and-release win: - """ + removeDir("\"%LIBS_DIR%\\Qt-5.15.13\"") + """ + """ + removeDir('"%LIBS_DIR%\\Qt-' + qt + '"') + """ SET ANGLE_DIR=%LIBS_DIR%\\tg_angle SET ANGLE_LIBS_DIR=%ANGLE_DIR%\\out SET MOZJPEG_DIR=%LIBS_DIR%\\mozjpeg @@ -1447,7 +1458,7 @@ win: SET OPENSSL_LIBS_DIR=%OPENSSL_DIR%\\out SET ZLIB_LIBS_DIR=%LIBS_DIR%\\zlib SET WEBP_DIR=%LIBS_DIR%\\libwebp - configure -prefix "%LIBS_DIR%\\Qt-5.15.13" ^ + configure -prefix "%LIBS_DIR%\\Qt-%QT%" ^ %CONFIGURATIONS% ^ -force-debug-info ^ -opensource ^ @@ -1482,14 +1493,14 @@ win: jom -j%NUMBER_OF_PROCESSORS% jom -j%NUMBER_OF_PROCESSORS% install mac: - find ../../patches/qtbase_5.15.13 -type f -print0 | sort -z | xargs -0 git apply + find ../../patches/qtbase_$QT -type f -print0 | sort -z | xargs -0 git apply cd .. CONFIGURATIONS=-debug release: CONFIGURATIONS=-debug-and-release mac: - ./configure -prefix "$USED_PREFIX/Qt-5.15.13" \ + ./configure -prefix "$USED_PREFIX/Qt-$QT" \ $CONFIGURATIONS \ -force-debug-info \ -opensource \ @@ -1508,16 +1519,16 @@ mac: make $MAKE_THREADS_CNT make install """) - -if buildQt6: - stage('qt_6_2_8', """ -mac: - git clone -b v6.2.8-lts-lgpl https://github.com/qt/qt5.git qt_6_2_8 - cd qt_6_2_8 +else: # qt > '6' + branch = 'v$QT' + ('-lts-lgpl' if qt < '6.3' else '') + stage('qt_' + qt, """ + git clone -b """ + branch + """ https://github.com/qt/qt5.git qt_$QT + cd qt_$QT git submodule update --init --recursive qtbase qtimageformats qtsvg -depends:patches/qtbase_6.2.8/*.patch +depends:patches/qtbase_""" + qt + """/*.patch cd qtbase - find ../../patches/qtbase_6.2.8 -type f -print0 | sort -z | xargs -0 git apply -v +mac: + find ../../patches/qtbase_$QT -type f -print0 | sort -z | xargs -0 git apply -v cd .. sed -i.bak 's/tqtc-//' {qtimageformats,qtsvg}/dependencies.yaml @@ -1525,7 +1536,7 @@ depends:patches/qtbase_6.2.8/*.patch release: CONFIGURATIONS=-debug-and-release mac: - ./configure -prefix "$USED_PREFIX/Qt-6.2.8" \ + ./configure -prefix "$USED_PREFIX/Qt-$QT" \ $CONFIGURATIONS \ -force-debug-info \ -opensource \ @@ -1546,6 +1557,62 @@ mac: ninja ninja install +win: + for /r %%i in (..\\..\\patches\\qtbase_%QT%\\*) do git apply %%i -v + cd .. + + SET CONFIGURATIONS=-debug +release: + SET CONFIGURATIONS=-debug-and-release +win: + """ + removeDir('"%LIBS_DIR%\\Qt' + qt + '"') + """ + SET MOZJPEG_DIR=%LIBS_DIR%\\mozjpeg + SET OPENSSL_DIR=%LIBS_DIR%\\openssl3 + SET OPENSSL_LIBS_DIR=%OPENSSL_DIR%\\out + SET ZLIB_LIBS_DIR=%LIBS_DIR%\\zlib + SET WEBP_DIR=%LIBS_DIR%\\libwebp + configure -prefix "%LIBS_DIR%\\Qt-%QT%" ^ + %CONFIGURATIONS% ^ + -force-debug-info ^ + -opensource ^ + -confirm-license ^ + -static ^ + -static-runtime ^ + -feature-c++20 ^ + -openssl linked ^ + -system-webp ^ + -system-zlib ^ + -system-libjpeg ^ + -nomake examples ^ + -nomake tests ^ + -platform win32-msvc ^ + -D ZLIB_WINAPI ^ + -- ^ + -D OPENSSL_FOUND=1 ^ + -D OPENSSL_INCLUDE_DIR="%OPENSSL_DIR%\\include" ^ + -D LIB_EAY_DEBUG="%OPENSSL_LIBS_DIR%.dbg\\libcrypto.lib" ^ + -D SSL_EAY_DEBUG="%OPENSSL_LIBS_DIR%.dbg\\libssl.lib" ^ + -D LIB_EAY_RELEASE="%OPENSSL_LIBS_DIR%\\libcrypto.lib" ^ + -D SSL_EAY_RELEASE="%OPENSSL_LIBS_DIR%\\libssl.lib" ^ + -D JPEG_FOUND=1 ^ + -D JPEG_INCLUDE_DIR="%MOZJPEG_DIR%" ^ + -D JPEG_LIBRARY_DEBUG="%MOZJPEG_DIR%\\Debug\\jpeg-static.lib" ^ + -D JPEG_LIBRARY_RELEASE="%MOZJPEG_DIR%\\Release\\jpeg-static.lib" ^ + -D ZLIB_FOUND=1 ^ + -D ZLIB_INCLUDE_DIR="%ZLIB_LIBS_DIR%" ^ + -D ZLIB_LIBRARY_DEBUG="%ZLIB_LIBS_DIR%\\Debug\\zlibstaticd.lib" ^ + -D ZLIB_LIBRARY_RELEASE="%ZLIB_LIBS_DIR%\\Release\\zlibstatic.lib" ^ + -D WebP_INCLUDE_DIR="%WEBP_DIR%\\src" ^ + -D WebP_demux_INCLUDE_DIR="%WEBP_DIR%\\src" ^ + -D WebP_mux_INCLUDE_DIR="%WEBP_DIR%\\src" ^ + -D WebP_LIBRARY="%WEBP_DIR%\\out\\release-static\\$X8664\\lib\\webp.lib" ^ + -D WebP_demux_LIBRARY="%WEBP_DIR%\\out\\release-static\\$X8664\\lib\\webpdemux.lib" ^ + -D WebP_mux_LIBRARY="%WEBP_DIR%\\out\\release-static\\$X8664\\lib\\webpmux.lib" + + cmake --build . --config Debug --parallel + cmake --install . --config Debug + cmake --build . --parallel + cmake --install . """) stage('tg_owt', """ diff --git a/Telegram/build/qt_version.py b/Telegram/build/qt_version.py new file mode 100644 index 000000000..a444a6049 --- /dev/null +++ b/Telegram/build/qt_version.py @@ -0,0 +1,17 @@ +import sys, os + +def resolve(arch): + if sys.platform == 'darwin': + os.environ['Qt'] = '6.2.8' + elif sys.platform == 'win32': + if arch == 'arm' or 'qt6' in sys.argv: + print('Choosing Qt 6.') + os.environ['QT'] = '6.7.2' + elif os.environ.get('QT') is None: + print('Choosing Qt 5.') + os.environ['QT'] = '5.15.13' + else: + print('Choosing Qt ' + os.environ.get('QT')) + else: + return False + return True diff --git a/Telegram/cmake/generate_midl.cmake b/Telegram/cmake/generate_midl.cmake index 51d960ba8..d3ec53cc5 100644 --- a/Telegram/cmake/generate_midl.cmake +++ b/Telegram/cmake/generate_midl.cmake @@ -8,7 +8,9 @@ function(generate_midl target_name src_loc) set(gen_dst ${CMAKE_CURRENT_BINARY_DIR}/gen) file(MAKE_DIRECTORY ${gen_dst}) - if (build_win64) + if (build_winarm) + set(env arm64) + elseif (build_win64) set(env x64) else() set(env win32) diff --git a/Telegram/cmake/td_scheme.cmake b/Telegram/cmake/td_scheme.cmake index cf241941b..3cf326489 100644 --- a/Telegram/cmake/td_scheme.cmake +++ b/Telegram/cmake/td_scheme.cmake @@ -34,6 +34,13 @@ PUBLIC desktop-app::lib_tl ) +if (WIN32 AND NOT build_win64 AND NOT build_winarm AND QT_VERSION GREATER 6) + target_compile_options(td_scheme + PRIVATE + /bigobj # scheme.cpp has too many sections. + ) +endif() + if (CMAKE_SYSTEM_PROCESSOR STREQUAL "mips64") # Sometimes final linking may fail with error "relocation truncated to fit" # due to large scheme size. diff --git a/Telegram/configure.py b/Telegram/configure.py index ceb27bfb9..a5819f222 100644 --- a/Telegram/configure.py +++ b/Telegram/configure.py @@ -11,6 +11,8 @@ sys.dont_write_bytecode = True scriptPath = os.path.dirname(os.path.realpath(__file__)) sys.path.append(scriptPath + '/../cmake') import run_cmake +sys.path.append(scriptPath + '/build') +import qt_version executePath = os.getcwd() def finish(code): @@ -41,12 +43,18 @@ if officialTarget in ['win', 'uwp']: arch = 'x86' elif officialTarget in ['win64', 'uwp64']: arch = 'x64' +elif officialTarget in ['winarm', 'uwparm']: + arch = 'arm' +if not qt_version.resolve(arch): + error('Usupported platform.') + +if 'qt6' in arguments: + arguments.remove('qt6') if officialTarget != '': officialApiIdFile = scriptPath + '/../../DesktopPrivate/custom_api_id.h' if not os.path.isfile(officialApiIdFile): - print("[ERROR] DesktopPrivate/custom_api_id.h not found.") - finish(1) + error('DesktopPrivate/custom_api_id.h not found.') with open(officialApiIdFile, 'r') as f: for line in f: apiIdMatch = re.search(r'ApiId\s+=\s+(\d+)', line) diff --git a/Telegram/lib_base b/Telegram/lib_base index 953b0bf54..324cbc7d1 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 953b0bf548f36213414e6f1a65dd3ebad618350e +Subproject commit 324cbc7d17b4fcacffc1faf958c5fa3d10d0e5da diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 0dd59f1a9..360464fd9 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 0dd59f1a9c740d86083a80291a5081e3a9e3ecf8 +Subproject commit 360464fd9d073d3919783ed800fb3b3682a261a0 diff --git a/cmake b/cmake index a7527c0e6..b92244f0c 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit a7527c0e6eba1c71cd0dfd7bd8de9c1e68cb529f +Subproject commit b92244f0c21f157600484498c33a3566087526dd From 002fe9a72aa220c24b443cfe98210a16263ffede Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 21 Jun 2024 11:54:49 +0400 Subject: [PATCH 030/142] Fix build. --- Telegram/SourceFiles/settings/settings_experimental.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Telegram/SourceFiles/settings/settings_experimental.cpp b/Telegram/SourceFiles/settings/settings_experimental.cpp index 34a97dfad..93e9e74dc 100644 --- a/Telegram/SourceFiles/settings/settings_experimental.cpp +++ b/Telegram/SourceFiles/settings/settings_experimental.cpp @@ -145,7 +145,6 @@ void SetupExperimental( addToggle(Core::kOptionFractionalScalingEnabled); addToggle(Window::kOptionViewProfileInChatsListContextMenu); addToggle(Info::Profile::kOptionShowPeerIdBelowAbout); - addToggle(Ui::GL::kOptionAllowX11NvidiaOpenGL); addToggle(Ui::kOptionUseSmallMsgBubbleRadius); addToggle(Media::Player::kOptionDisableAutoplayNext); addToggle(kOptionSendLargePhotos); From acee7c7cfc2a770127501198e585e3becad459e4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 21 Jun 2024 13:25:48 +0400 Subject: [PATCH 031/142] More delay-load libraries on Windows with Qt 6. --- Telegram/CMakeLists.txt | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 20aa790eb..6c4bce205 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1829,6 +1829,7 @@ if (WIN32) /DELAYLOAD:crypt32.dll /DELAYLOAD:bcrypt.dll /DELAYLOAD:netapi32.dll + /DELAYLOAD:imm32.dll /DELAYLOAD:userenv.dll /DELAYLOAD:wtsapi32.dll /DELAYLOAD:propsys.dll @@ -1836,19 +1837,33 @@ if (WIN32) if (QT_VERSION GREATER 6) target_link_options(Telegram PRIVATE + /DELAYLOAD:API-MS-Win-EventLog-Legacy-l1-1-0.dll + /DELAYLOAD:API-MS-Win-Core-Console-l1-1-0.dll + /DELAYLOAD:API-MS-Win-Core-Fibers-l2-1-0.dll + /DELAYLOAD:API-MS-Win-Core-Fibers-l2-1-1.dll + /DELAYLOAD:API-MS-Win-Core-File-l1-1-0.dll + /DELAYLOAD:API-MS-Win-Core-LibraryLoader-l1-2-0.dll + /DELAYLOAD:API-MS-Win-Core-Localization-l1-2-0.dll + /DELAYLOAD:API-MS-Win-Core-Memory-l1-1-0.dll + /DELAYLOAD:API-MS-Win-Core-Memory-l1-1-1.dll + /DELAYLOAD:API-MS-Win-Core-ProcessThreads-l1-1-0.dll /DELAYLOAD:API-MS-Win-Core-Synch-l1-2-0.dll # Synchronization.lib + /DELAYLOAD:API-MS-Win-Core-SysInfo-l1-1-0.dll + /DELAYLOAD:API-MS-Win-Core-Timezone-l1-1-0.dll + /DELAYLOAD:API-MS-Win-Core-WinRT-l1-1-0.dll + /DELAYLOAD:API-MS-Win-Core-WinRT-Error-l1-1-0.dll + /DELAYLOAD:API-MS-Win-Core-WinRT-String-l1-1-0.dll + /DELAYLOAD:API-MS-Win-Security-CryptoAPI-l1-1-0.dll + /DELAYLOAD:API-MS-Win-Shcore-Scaling-l1-1-1.dll /DELAYLOAD:authz.dll # Authz.lib + /DELAYLOAD:comdlg32.dll /DELAYLOAD:dwrite.dll # DWrite.lib /DELAYLOAD:dxgi.dll # DXGI.lib /DELAYLOAD:d3d9.dll # D3D9.lib /DELAYLOAD:d3d11.dll # D3D11.lib /DELAYLOAD:d3d12.dll # D3D12.lib /DELAYLOAD:setupapi.dll # SetupAPI.lib - ) - else() - target_link_options(Telegram - PRIVATE - /DELAYLOAD:imm32.dll + /DELAYLOAD:winhttp.dll ) endif() endif() From c3ed5224c2dff6aac9b32e6a454a6ad9f686334a Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 21 Jun 2024 13:57:21 +0400 Subject: [PATCH 032/142] Use tripple-enter to jump out of a block. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 360464fd9..8c6ec7663 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 360464fd9d073d3919783ed800fb3b3682a261a0 +Subproject commit 8c6ec766394cdbc04aef9c32664153261c61f7ce From 46157c99c4659119b938676f6edfc981da6804e7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 21 Jun 2024 13:57:31 +0400 Subject: [PATCH 033/142] Allow saving empty syntax highlight language. --- Telegram/SourceFiles/chat_helpers/message_field.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 465830fd1..38435758e 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -252,7 +252,7 @@ void EditCodeLanguageBox( const auto callback = [=] { const auto name = field->getLastText().trimmed(); - const auto check = QRegularExpression("^[a-zA-Z0-9\\+\\-]+$"); + const auto check = QRegularExpression("^[a-zA-Z0-9\\+\\-]*$"); if (check.match(name).hasMatch()) { auto weak = Ui::MakeWeak(box); save(name); From 2db81211c8c14c6a75affd49313859a55f61aa28 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 21 Jun 2024 22:37:37 +0400 Subject: [PATCH 034/142] Fix configure on macOS. --- Telegram/build/qt_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/build/qt_version.py b/Telegram/build/qt_version.py index a444a6049..0f1bf164b 100644 --- a/Telegram/build/qt_version.py +++ b/Telegram/build/qt_version.py @@ -2,7 +2,7 @@ import sys, os def resolve(arch): if sys.platform == 'darwin': - os.environ['Qt'] = '6.2.8' + os.environ['QT'] = '6.2.8' elif sys.platform == 'win32': if arch == 'arm' or 'qt6' in sys.argv: print('Choosing Qt 6.') From 382b175db2fc2e55192a4f430e062da0ce11646a Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 21 Jun 2024 16:46:34 +0400 Subject: [PATCH 035/142] Fix build on Linux --- Telegram/build/qt_version.py | 5 ++--- Telegram/configure.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Telegram/build/qt_version.py b/Telegram/build/qt_version.py index 0f1bf164b..1703e15d0 100644 --- a/Telegram/build/qt_version.py +++ b/Telegram/build/qt_version.py @@ -10,8 +10,7 @@ def resolve(arch): elif os.environ.get('QT') is None: print('Choosing Qt 5.') os.environ['QT'] = '5.15.13' - else: - print('Choosing Qt ' + os.environ.get('QT')) - else: + elif os.environ.get('QT') is None: return False + print('Choosing Qt ' + os.environ.get('QT')) return True diff --git a/Telegram/configure.py b/Telegram/configure.py index a5819f222..f31dd70a7 100644 --- a/Telegram/configure.py +++ b/Telegram/configure.py @@ -46,7 +46,7 @@ elif officialTarget in ['win64', 'uwp64']: elif officialTarget in ['winarm', 'uwparm']: arch = 'arm' if not qt_version.resolve(arch): - error('Usupported platform.') + error('Unsupported platform.') if 'qt6' in arguments: arguments.remove('qt6') From 2b4a2b5b97a498f5a67206d6219ddb02d55891aa Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sat, 22 Jun 2024 09:19:22 +0400 Subject: [PATCH 036/142] Fix a crash in Dialogs::Widget::setupStories --- Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 7caeb6117..29e0400d7 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -896,13 +896,15 @@ void Widget::setupStories() { Core::App().settings().setStoriesClickTooltipHidden(true); Core::App().saveSettingsDelayed(); }; - _stories->setShowTooltip( - controller()->content(), - rpl::combine( - Core::App().settings().storiesClickTooltipHiddenValue(), - shownValue(), - !rpl::mappers::_1 && rpl::mappers::_2), - hideTooltip); + InvokeQueued(_stories.get(), [=] { + _stories->setShowTooltip( + controller()->content(), + rpl::combine( + Core::App().settings().storiesClickTooltipHiddenValue(), + shownValue(), + !rpl::mappers::_1 && rpl::mappers::_2), + hideTooltip); + }); } _storiesContents.fire(Stories::ContentForSession( From b58ece3a38964aa9a1822b52a1bfa1ec2da8dc3a Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sat, 22 Jun 2024 09:14:55 +0400 Subject: [PATCH 037/142] Update lib_ui --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 8c6ec7663..ca1886776 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 8c6ec766394cdbc04aef9c32664153261c61f7ce +Subproject commit ca18867762fb0a3e8ecede2ae8e42f4cbdc057f6 From d2d5226dc709d394ae6c33b2ea683a1aebc43b9b Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 21 Jun 2024 14:35:26 +0400 Subject: [PATCH 038/142] Update exposed state for connecting widgets without QWindow events --- .../view/history_view_top_bar_widget.cpp | 16 +++------- .../window/window_connecting_widget.cpp | 31 ++++++++++++------- .../window/window_connecting_widget.h | 1 - 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 59a94d585..df5d902c2 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -55,7 +55,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_send_action.h" #include "chat_helpers/emoji_interactions.h" #include "base/unixtime.h" -#include "base/event_filter.h" #include "support/support_helper.h" #include "apiwrap.h" #include "api/api_chat_participants.h" @@ -233,16 +232,6 @@ TopBarWidget::TopBarWidget( updateConnectingState(); }, lifetime()); - base::install_event_filter( - this, - window()->windowHandle(), - [=](not_null e) { - if (e->type() == QEvent::Expose) { - updateConnectingState(); - } - return base::EventFilterResult::Continue; - }); - setCursor(style::cur_pointer); } @@ -254,7 +243,8 @@ Main::Session &TopBarWidget::session() const { void TopBarWidget::updateConnectingState() { const auto state = _controller->session().mtp().dcstate(); - const auto exposed = window()->windowHandle()->isExposed(); + const auto exposed = window()->windowHandle() + && window()->windowHandle()->isExposed(); if (state == MTP::ConnectedState || !exposed) { if (_connecting) { _connecting = nullptr; @@ -271,6 +261,7 @@ void TopBarWidget::updateConnectingState() { void TopBarWidget::connectingAnimationCallback() { if (!anim::Disabled()) { + updateConnectingState(); update(); } } @@ -436,6 +427,7 @@ void TopBarWidget::paintEvent(QPaintEvent *e) { if (_animatingMode) { return; } + updateConnectingState(); Painter p(this); const auto selectedButtonsTop = countSelectedButtonsTop( diff --git a/Telegram/SourceFiles/window/window_connecting_widget.cpp b/Telegram/SourceFiles/window/window_connecting_widget.cpp index a7b1f3a1f..9940287fa 100644 --- a/Telegram/SourceFiles/window/window_connecting_widget.cpp +++ b/Telegram/SourceFiles/window/window_connecting_widget.cpp @@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/window_connecting_widget.h" -#include "base/event_filter.h" #include "ui/widgets/buttons.h" #include "ui/effects/radial_animation.h" #include "ui/painter.h" @@ -37,6 +36,8 @@ class Progress : public Ui::RpWidget { public: Progress(QWidget *parent); + rpl::producer<> animationStepRequests() const; + protected: void paintEvent(QPaintEvent *e) override; @@ -44,6 +45,7 @@ private: void animationStep(); Ui::InfiniteRadialAnimation _animation; + rpl::event_stream<> _animationStepRequests; }; @@ -71,10 +73,15 @@ void Progress::paintEvent(QPaintEvent *e) { void Progress::animationStep() { if (!anim::Disabled()) { + _animationStepRequests.fire({}); update(); } } +rpl::producer<> Progress::animationStepRequests() const { + return _animationStepRequests.events(); +} + } // namespace class ConnectionState::Widget : public Ui::AbstractButton { @@ -109,7 +116,7 @@ private: const not_null _account; Layout _currentLayout; base::unique_qptr _retry; - QPointer _progress; + QPointer _progress; QPointer _proxyIcon; rpl::event_stream<> _refreshStateRequests; @@ -210,14 +217,6 @@ ConnectionState::ConnectionState( rpl::producer shown) : _account(account) , _parent(parent) -, _exposeFilter(base::install_event_filter( - parent->window()->windowHandle(), - [=](not_null e) { - if (e->type() == QEvent::Expose) { - refreshState(); - } - return base::EventFilterResult::Continue; - })) , _refreshTimer([=] { refreshState(); }) , _currentLayout(computeLayout(_state)) { rpl::combine( @@ -241,7 +240,9 @@ ConnectionState::ConnectionState( }, _lifetime); } - Core::App().settings().proxy().connectionTypeValue( + rpl::combine( + Core::App().settings().proxy().connectionTypeValue(), + rpl::single(QRect()) | rpl::then(_parent->paintRequest()) ) | rpl::start_with_next([=] { refreshState(); }, _lifetime); @@ -301,7 +302,8 @@ void ConnectionState::setBottomSkip(int skip) { void ConnectionState::refreshState() { using Checker = Core::UpdateChecker; const auto state = [&]() -> State { - const auto exposed = _parent->window()->windowHandle()->isExposed(); + const auto exposed = _parent->window()->windowHandle() + && _parent->window()->windowHandle()->isExposed(); const auto under = _widget && _widget->isOver(); const auto ready = (Checker().state() == Checker::State::Ready); const auto state = _account->mtp().dcstate(); @@ -501,6 +503,11 @@ ConnectionState::Widget::Widget( addClickHandler([=] { Ui::show(ProxiesBoxController::CreateOwningBox(account)); }); + + _progress->animationStepRequests( + ) | rpl::start_with_next([=] { + _refreshStateRequests.fire({}); + }, _progress->lifetime()); } void ConnectionState::Widget::onStateChanged( diff --git a/Telegram/SourceFiles/window/window_connecting_widget.h b/Telegram/SourceFiles/window/window_connecting_widget.h index 7813ce5ad..891a91072 100644 --- a/Telegram/SourceFiles/window/window_connecting_widget.h +++ b/Telegram/SourceFiles/window/window_connecting_widget.h @@ -80,7 +80,6 @@ private: const not_null _account; not_null _parent; - base::unique_qptr _exposeFilter; rpl::variable _bottomSkip; base::unique_qptr _widget; bool _forceHidden = false; From 671a15d763b73d7c5a27a7f899b1ca8f33dba3b4 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 21 Jun 2024 14:36:17 +0400 Subject: [PATCH 039/142] Update skip taskbar hint on Linux without QWindow events --- .../platform/linux/main_window_linux.cpp | 50 +++++-------------- .../platform/linux/main_window_linux.h | 4 +- Telegram/SourceFiles/window/main_window.cpp | 2 - 3 files changed, 14 insertions(+), 42 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 764b90bb6..9a8d3feeb 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -136,35 +136,6 @@ MainWindow::MainWindow(not_null controller) : Window::MainWindow(controller) { } -void MainWindow::initHook() { - events() | rpl::start_with_next([=](not_null e) { - if (e->type() == QEvent::ThemeChange) { - updateWindowIcon(); - } - }, lifetime()); - - base::install_event_filter(windowHandle(), [=](not_null e) { - if (e->type() == QEvent::Expose) { - auto ee = static_cast(e.get()); - if (ee->region().isNull()) { - return base::EventFilterResult::Continue; - } - if (!windowHandle() - || windowHandle()->parent() - || !windowHandle()->isVisible()) { - return base::EventFilterResult::Continue; - } - handleNativeSurfaceChanged(true); - } else if (e->type() == QEvent::Hide) { - if (!windowHandle() || windowHandle()->parent()) { - return base::EventFilterResult::Continue; - } - handleNativeSurfaceChanged(false); - } - return base::EventFilterResult::Continue; - }); -} - void MainWindow::workmodeUpdated(Core::Settings::WorkMode mode) { if (!TrayIconSupported()) { return; @@ -514,19 +485,22 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *evt) { updateGlobalMenu(); } } + } else if (obj == this && t == QEvent::Paint) { + if (!_exposed) { + _exposed = true; + SkipTaskbar( + windowHandle(), + (Core::App().settings().workMode() == WorkMode::TrayOnly) + && TrayIconSupported()); + } + } else if (obj == this && t == QEvent::Hide) { + _exposed = false; + } else if (obj == this && t == QEvent::ThemeChange) { + updateWindowIcon(); } return Window::MainWindow::eventFilter(obj, evt); } -void MainWindow::handleNativeSurfaceChanged(bool exist) { - if (exist) { - SkipTaskbar( - windowHandle(), - (Core::App().settings().workMode() == WorkMode::TrayOnly) - && TrayIconSupported()); - } -} - MainWindow::~MainWindow() { } diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.h b/Telegram/SourceFiles/platform/linux/main_window_linux.h index 311ed3a74..4ef98ba77 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.h +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.h @@ -28,7 +28,6 @@ public: protected: bool eventFilter(QObject *obj, QEvent *evt) override; - void initHook() override; void unreadCounterChangedHook() override; void updateGlobalMenuHook() override; @@ -37,7 +36,6 @@ protected: private: void updateUnityCounter(); - void handleNativeSurfaceChanged(bool exist); QMenuBar *psMainMenu = nullptr; QAction *psLogout = nullptr; @@ -61,6 +59,8 @@ private: QAction *psMonospace = nullptr; QAction *psClearFormat = nullptr; + bool _exposed = false; + }; [[nodiscard]] inline int32 ScreenNameChecksum(const QString &name) { diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index f1b1f7c0b..1276b264f 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -455,8 +455,6 @@ QRect MainWindow::desktopRect() const { } void MainWindow::init() { - createWinId(); - initHook(); updatePalette(); From 490ec7949f0008f46132d5c12346ad3e191ca2a1 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 21 Jun 2024 14:37:37 +0400 Subject: [PATCH 040/142] Don't call hide/createWinId just to call show right after that in Window::Notifications::Default::HideAllButton --- Telegram/SourceFiles/window/notifications_manager_default.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index 7b4ba2306..232d972ab 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -1231,8 +1231,6 @@ HideAllButton::HideAllButton( auto position = computePosition(st::notifyHideAllHeight); updateGeometry(position.x(), position.y(), st::notifyWidth, st::notifyHideAllHeight); - hide(); - createWinId(); style::PaletteChanged( ) | rpl::start_with_next([=] { From 5180e0ec57f39f576a46842f1556fbd338f428a4 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 21 Jun 2024 15:05:27 +0400 Subject: [PATCH 041/142] Use QWidget::setScreen with Qt 6 --- .../calls/group/ui/desktop_capture_choose_source.cpp | 6 +++++- .../SourceFiles/media/view/media_view_overlay_widget.cpp | 4 ++++ Telegram/SourceFiles/media/view/media_view_pip.cpp | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp index 93c5ecf1e..cf1375268 100644 --- a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp +++ b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp @@ -585,7 +585,6 @@ void ChooseSourceProcess::setupSourcesGeometry() { void ChooseSourceProcess::setupGeometryWithParent( not_null parent) { - _window->createWinId(); const auto parentScreen = [&] { if (const auto screen = QGuiApplication::screenAt( parent->geometry().center())) { @@ -595,7 +594,12 @@ void ChooseSourceProcess::setupGeometryWithParent( }(); const auto myScreen = _window->screen(); if (parentScreen && myScreen != parentScreen) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + _window->setScreen(parentScreen); +#else // Qt >= 6.0.0 + _window->createWinId(); _window->windowHandle()->setScreen(parentScreen); +#endif // Qt < 6.0.0 } _window->setFixedSize(_fixedSize); _window->move( diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 065c91ebc..9b71b7a3f 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -804,7 +804,11 @@ void OverlayWidget::moveToScreen(bool inMove) { DEBUG_LOG(("Viewer Pos: Currently on screen %1, moving to screen %2") .arg(screenList.indexOf(myScreen)) .arg(screenList.indexOf(activeWindowScreen))); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + _window->setScreen(activeWindowScreen); +#else // Qt >= 6.0.0 window()->setScreen(activeWindowScreen); +#endif // Qt < 6.0.0 DEBUG_LOG(("Viewer Pos: New actual screen: %1") .arg(screenList.indexOf(_window->screen()))); } diff --git a/Telegram/SourceFiles/media/view/media_view_pip.cpp b/Telegram/SourceFiles/media/view/media_view_pip.cpp index f2bc74550..92c98b82b 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.cpp +++ b/Telegram/SourceFiles/media/view/media_view_pip.cpp @@ -514,7 +514,11 @@ void PipPanel::setPositionDefault() { const auto parentScreen = widgetScreen(_parent); const auto myScreen = widget()->screen(); if (parentScreen && myScreen != parentScreen) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + widget()->setScreen(parentScreen); +#else // Qt >= 6.0.0 widget()->windowHandle()->setScreen(parentScreen); +#endif // Qt < 6.0.0 } auto position = Position(); position.snapped = RectPart::Top | RectPart::Left; From 006d6fe2c09d614b847078cd74b0360bf0806283 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 21 Jun 2024 15:07:37 +0400 Subject: [PATCH 042/142] Use RpWidget::screenValue --- .../media/view/media_view_overlay_widget.cpp | 10 +++++----- .../media/view/media_view_overlay_widget.h | 2 +- .../SourceFiles/media/view/media_view_pip.cpp | 12 +++++------- .../SourceFiles/media/view/media_view_pip.h | 2 +- .../SourceFiles/window/window_main_menu.cpp | 18 ++++++------------ 5 files changed, 18 insertions(+), 26 deletions(-) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 9b71b7a3f..18e47b4fb 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -633,10 +633,10 @@ OverlayWidget::OverlayWidget() _widget->setMouseTracking(true); _window->createWinId(); - QObject::connect( - window(), - &QWindow::screenChanged, - [=](QScreen *screen) { handleScreenChanged(screen); }); + _window->screenValue( + ) | rpl::skip(1) | rpl::start_with_next([=](not_null screen) { + handleScreenChanged(screen); + }, lifetime()); subscribeToScreenGeometry(); updateGeometry(); updateControlsGeometry(); @@ -2315,7 +2315,7 @@ void OverlayWidget::dropdownHidden() { } } -void OverlayWidget::handleScreenChanged(QScreen *screen) { +void OverlayWidget::handleScreenChanged(not_null screen) { subscribeToScreenGeometry(); if (isHidden()) { return; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index 02bdca5fb..1bac50ff3 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -303,7 +303,7 @@ private: bool moveToNext(int delta); void preloadData(int delta); - void handleScreenChanged(QScreen *screen); + void handleScreenChanged(not_null screen); [[nodiscard]] bool computeSaveButtonVisible() const; void checkForSaveLoaded(); diff --git a/Telegram/SourceFiles/media/view/media_view_pip.cpp b/Telegram/SourceFiles/media/view/media_view_pip.cpp index 92c98b82b..8fd911964 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.cpp +++ b/Telegram/SourceFiles/media/view/media_view_pip.cpp @@ -361,12 +361,10 @@ void PipPanel::init() { Ui::Platform::ClearTransientParent(widget()); }, rp()->lifetime()); - QObject::connect( - widget()->windowHandle(), - &QWindow::screenChanged, - [=](QScreen *screen) { - handleScreenChanged(screen); - }); + rp()->screenValue( + ) | rpl::skip(1) | rpl::start_with_next([=](not_null screen) { + handleScreenChanged(screen); + }, rp()->lifetime()); if (Platform::IsWayland()) { rp()->sizeValue( @@ -637,7 +635,7 @@ void PipPanel::handleWaylandResize(QSize size) { _inHandleWaylandResize = false; } -void PipPanel::handleScreenChanged(QScreen *screen) { +void PipPanel::handleScreenChanged(not_null screen) { const auto screenGeometry = screen->availableGeometry(); const auto minimalSize = _ratio.scaled( st::pipMinimalSize, diff --git a/Telegram/SourceFiles/media/view/media_view_pip.h b/Telegram/SourceFiles/media/view/media_view_pip.h index dcc251ec3..efcb326f4 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.h +++ b/Telegram/SourceFiles/media/view/media_view_pip.h @@ -77,7 +77,7 @@ public: [[nodiscard]] bool dragging() const; void handleWaylandResize(QSize size); - void handleScreenChanged(QScreen *screen); + void handleScreenChanged(not_null screen); void handleMousePress(QPoint position, Qt::MouseButton button); void handleMouseRelease(QPoint position, Qt::MouseButton button); void handleMouseMove(QPoint position); diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index bf1b21962..bcddeda4d 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -957,21 +957,15 @@ void MainMenu::drawName(Painter &p) { } void MainMenu::initResetScaleButton() { - if (!window() || !window()->windowHandle()) { - return; - } - const auto handle = window()->windowHandle(); - rpl::single( - handle->screen() - ) | rpl::then( - base::qt_signal_producer(handle, &QWindow::screenChanged) - ) | rpl::filter([](QScreen *screen) { - return screen != nullptr; - }) | rpl::map([](QScreen * screen) { + _controller->widget()->screenValue( + ) | rpl::map([](not_null screen) { return rpl::single( screen->availableGeometry() ) | rpl::then( - base::qt_signal_producer(screen, &QScreen::availableGeometryChanged) + base::qt_signal_producer( + screen.get(), + &QScreen::availableGeometryChanged + ) ); }) | rpl::flatten_latest( ) | rpl::map([](QRect available) { From eea50ed6b02fe84e6392ee509e8e0e8dfb56d5d0 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 21 Jun 2024 16:59:10 +0400 Subject: [PATCH 043/142] Delay UpdatePowerSaveBlocker to show in media viewer --- .../media/view/media_view_overlay_widget.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 18e47b4fb..297c464ff 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -631,7 +631,6 @@ OverlayWidget::OverlayWidget() _window->setWindowFlags(Qt::FramelessWindowHint | Qt::Tool); } _widget->setMouseTracking(true); - _window->createWinId(); _window->screenValue( ) | rpl::skip(1) | rpl::start_with_next([=](not_null screen) { @@ -3823,12 +3822,17 @@ void OverlayWidget::updatePowerSaveBlocker( && _document->isVideoFile() && !IsPausedOrPausing(state.state) && !IsStoppedOrStopping(state.state); - base::UpdatePowerSaveBlocker( - _streamed->powerSaveBlocker, - block, - base::PowerSaveBlockType::PreventDisplaySleep, - [] { return u"Video playback is active"_q; }, - [=] { return window(); }); + + _window->shownValue() | rpl::filter([=](bool shown) { + return shown; + }) | rpl::take(1) | rpl::start_with_next([=] { + base::UpdatePowerSaveBlocker( + _streamed->powerSaveBlocker, + block, + base::PowerSaveBlockType::PreventDisplaySleep, + [] { return u"Video playback is active"_q; }, + [=] { return window(); }); + }, lifetime()); } QImage OverlayWidget::transformedShownContent() const { From c1bc7e6ab1e75bf591cde91bd1b5828dfd5c33ae Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 21 Jun 2024 17:06:02 +0400 Subject: [PATCH 044/142] Simplify PipPanel::handleWaylandResize --- .../SourceFiles/media/view/media_view_pip.cpp | 31 ++++--------------- .../SourceFiles/media/view/media_view_pip.h | 1 - 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/Telegram/SourceFiles/media/view/media_view_pip.cpp b/Telegram/SourceFiles/media/view/media_view_pip.cpp index 8fd911964..0975b5c6f 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.cpp +++ b/Telegram/SourceFiles/media/view/media_view_pip.cpp @@ -24,7 +24,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "base/platform/base_platform_info.h" #include "base/power_save_blocker.h" -#include "base/event_filter.h" #include "ui/platform/ui_platform_utility.h" #include "ui/platform/ui_platform_window_title.h" #include "ui/widgets/buttons.h" @@ -351,7 +350,6 @@ void PipPanel::init() { widget()->setMouseTracking(true); widget()->resize(0, 0); widget()->hide(); - widget()->createWinId(); rp()->shownValue( ) | rpl::filter([=](bool shown) { @@ -368,30 +366,9 @@ void PipPanel::init() { if (Platform::IsWayland()) { rp()->sizeValue( - ) | rpl::start_with_next([=](QSize size) { + ) | rpl::skip(1) | rpl::start_with_next([=](QSize size) { handleWaylandResize(size); }, rp()->lifetime()); - - base::install_event_filter(widget(), [=](not_null event) { - if (event->type() == QEvent::Resize && _inHandleWaylandResize) { - return base::EventFilterResult::Cancel; - } - return base::EventFilterResult::Continue; - }); - - base::install_event_filter(widget()->windowHandle(), [=](not_null event) { - if (event->type() == QEvent::Resize) { - if (_inHandleWaylandResize) { - return base::EventFilterResult::Cancel; - } - const auto newSize = static_cast(event.get())->size(); - if (_suggestedWaylandSize == newSize) { - handleWaylandResize(newSize); - return base::EventFilterResult::Cancel; - } - } - return base::EventFilterResult::Continue; - }); } } @@ -610,8 +587,10 @@ void PipPanel::setGeometry(QRect geometry) { } void PipPanel::handleWaylandResize(QSize size) { + if (_inHandleWaylandResize) { + return; + } _inHandleWaylandResize = true; - _suggestedWaylandSize = size; // Apply aspect ratio. const auto max = std::max(size.width(), size.height()); @@ -632,6 +611,8 @@ void PipPanel::handleWaylandResize(QSize size) { : scaled; widget()->resize(normalized); + QResizeEvent e(normalized, size); + QCoreApplication::sendEvent(widget()->windowHandle(), &e); _inHandleWaylandResize = false; } diff --git a/Telegram/SourceFiles/media/view/media_view_pip.h b/Telegram/SourceFiles/media/view/media_view_pip.h index efcb326f4..d0156e1bd 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.h +++ b/Telegram/SourceFiles/media/view/media_view_pip.h @@ -106,7 +106,6 @@ private: bool _useTransparency = true; bool _dragDisabled = false; bool _inHandleWaylandResize = false; - QSize _suggestedWaylandSize; style::margins _padding; RectPart _overState = RectPart(); From ba8991283405b28bba652ca51c17dfeeea10287d Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 24 Jun 2024 13:21:33 +0400 Subject: [PATCH 045/142] Switch more XCB code to base::Platform::XCB::Connection --- Telegram/SourceFiles/platform/linux/main_window_linux.cpp | 4 ++-- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 9a8d3feeb..e4507c795 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -51,8 +51,8 @@ using WorkMode = Core::Settings::WorkMode; #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION void XCBSkipTaskbar(QWindow *window, bool skip) { - const auto connection = base::Platform::XCB::GetConnectionFromQt(); - if (!connection) { + const base::Platform::XCB::Connection connection; + if (!connection || xcb_connection_has_error(connection)) { return; } diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index c874ae71d..474f47746 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -564,7 +564,7 @@ bool SkipTaskbarSupported() { #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION if (IsX11()) { return base::Platform::XCB::IsSupportedByWM( - base::Platform::XCB::GetConnectionFromQt(), + base::Platform::XCB::Connection(), "_NET_WM_STATE_SKIP_TASKBAR"); } #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION From 648b23b548eee61b05c9f2d0b8fac2bff28468a6 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 24 Jun 2024 13:59:06 +0400 Subject: [PATCH 046/142] Do non-getting XCB requests synchronously --- .../platform/linux/main_window_linux.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index e4507c795..1eb4f59d0 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -89,13 +89,16 @@ void XCBSkipTaskbar(QWindow *window, bool skip) { xev.data.data32[3] = 0; xev.data.data32[4] = 0; - xcb_send_event( - connection, - false, - root, - XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT - | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, - reinterpret_cast(&xev)); + free( + xcb_request_check( + connection, + xcb_send_event_checked( + connection, + false, + root, + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT + | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, + reinterpret_cast(&xev)))); } #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION From 3498a7f0ee5bbb567b1982f84cee0b94efd3c558 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 24 Jun 2024 16:17:16 +0400 Subject: [PATCH 047/142] Update submodules --- Telegram/lib_base | 2 +- Telegram/lib_ui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/lib_base b/Telegram/lib_base index 324cbc7d1..1be2a262a 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 324cbc7d17b4fcacffc1faf958c5fa3d10d0e5da +Subproject commit 1be2a262a6524d6dec3b616c2e9fd2ce42c9e61a diff --git a/Telegram/lib_ui b/Telegram/lib_ui index ca1886776..6ec776584 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit ca18867762fb0a3e8ecede2ae8e42f4cbdc057f6 +Subproject commit 6ec776584efd981ee2af3f231f4368ecf9161099 From 10c3fe0f637ea1a222102e8df4cf5727bdd2ab5d Mon Sep 17 00:00:00 2001 From: mrbesen Date: Wed, 29 May 2024 09:35:23 +0200 Subject: [PATCH 048/142] add option to export html and json in global export --- Telegram/SourceFiles/export/view/export_view_settings.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/export/view/export_view_settings.cpp b/Telegram/SourceFiles/export/view/export_view_settings.cpp index 8f546260a..6cd12298f 100644 --- a/Telegram/SourceFiles/export/view/export_view_settings.cpp +++ b/Telegram/SourceFiles/export/view/export_view_settings.cpp @@ -283,6 +283,7 @@ void SettingsWidget::setupPathAndFormat( addLocationLabel(container); addFormatOption(tr::lng_export_option_html(tr::now), Format::Html); addFormatOption(tr::lng_export_option_json(tr::now), Format::Json); + addFormatOption(tr::lng_export_option_html_and_json(tr::now), Format::HtmlAndJson); } void SettingsWidget::addLocationLabel( From 30819509d36964ba594fe6dd6d620e236e8410fa Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 25 Jun 2024 20:18:40 +0400 Subject: [PATCH 049/142] Set shortcut context for global menu shortcuts --- .../platform/linux/main_window_linux.cpp | 46 ++++++++++++++++--- .../platform/mac/main_window_mac.mm | 40 ++++++++++++---- 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 1eb4f59d0..7b17fcc77 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -112,16 +112,18 @@ void SkipTaskbar(QWindow *window, bool skip) { } void SendKeySequence( - Qt::Key key, - Qt::KeyboardModifiers modifiers = Qt::NoModifier) { - const auto focused = static_cast(QApplication::focusWidget()); + Qt::Key key, + Qt::KeyboardModifiers modifiers = Qt::NoModifier) { + const auto focused = QApplication::focusWidget(); if (qobject_cast(focused) || qobject_cast(focused) || dynamic_cast(focused)) { - QKeyEvent pressEvent(QEvent::KeyPress, key, modifiers); - focused->event(&pressEvent); - QKeyEvent releaseEvent(QEvent::KeyRelease, key, modifiers); - focused->event(&releaseEvent); + QApplication::postEvent( + focused, + new QKeyEvent(QEvent::KeyPress, key, modifiers)); + QApplication::postEvent( + focused, + new QKeyEvent(QEvent::KeyRelease, key, modifiers)); } } @@ -232,6 +234,7 @@ void MainWindow::createGlobalMenu() { QKeySequence::Quit); quit->setMenuRole(QAction::QuitRole); + quit->setShortcutContext(Qt::WidgetShortcut); auto edit = psMainMenu->addMenu(tr::lng_mac_menu_edit(tr::now)); @@ -240,6 +243,8 @@ void MainWindow::createGlobalMenu() { [] { SendKeySequence(Qt::Key_Z, Qt::ControlModifier); }, QKeySequence::Undo); + psUndo->setShortcutContext(Qt::WidgetShortcut); + psRedo = edit->addAction( tr::lng_linux_menu_redo(tr::now), [] { @@ -249,6 +254,8 @@ void MainWindow::createGlobalMenu() { }, QKeySequence::Redo); + psRedo->setShortcutContext(Qt::WidgetShortcut); + edit->addSeparator(); psCut = edit->addAction( @@ -256,21 +263,29 @@ void MainWindow::createGlobalMenu() { [] { SendKeySequence(Qt::Key_X, Qt::ControlModifier); }, QKeySequence::Cut); + psCut->setShortcutContext(Qt::WidgetShortcut); + psCopy = edit->addAction( tr::lng_mac_menu_copy(tr::now), [] { SendKeySequence(Qt::Key_C, Qt::ControlModifier); }, QKeySequence::Copy); + psCopy->setShortcutContext(Qt::WidgetShortcut); + psPaste = edit->addAction( tr::lng_mac_menu_paste(tr::now), [] { SendKeySequence(Qt::Key_V, Qt::ControlModifier); }, QKeySequence::Paste); + psPaste->setShortcutContext(Qt::WidgetShortcut); + psDelete = edit->addAction( tr::lng_mac_menu_delete(tr::now), [] { SendKeySequence(Qt::Key_Delete); }, QKeySequence(Qt::ControlModifier | Qt::Key_Backspace)); + psDelete->setShortcutContext(Qt::WidgetShortcut); + edit->addSeparator(); psBold = edit->addAction( @@ -278,16 +293,22 @@ void MainWindow::createGlobalMenu() { [] { SendKeySequence(Qt::Key_B, Qt::ControlModifier); }, QKeySequence::Bold); + psBold->setShortcutContext(Qt::WidgetShortcut); + psItalic = edit->addAction( tr::lng_menu_formatting_italic(tr::now), [] { SendKeySequence(Qt::Key_I, Qt::ControlModifier); }, QKeySequence::Italic); + psItalic->setShortcutContext(Qt::WidgetShortcut); + psUnderline = edit->addAction( tr::lng_menu_formatting_underline(tr::now), [] { SendKeySequence(Qt::Key_U, Qt::ControlModifier); }, QKeySequence::Underline); + psUnderline->setShortcutContext(Qt::WidgetShortcut); + psStrikeOut = edit->addAction( tr::lng_menu_formatting_strike_out(tr::now), [] { @@ -297,6 +318,8 @@ void MainWindow::createGlobalMenu() { }, Ui::kStrikeOutSequence); + psStrikeOut->setShortcutContext(Qt::WidgetShortcut); + psBlockquote = edit->addAction( tr::lng_menu_formatting_blockquote(tr::now), [] { @@ -306,6 +329,8 @@ void MainWindow::createGlobalMenu() { }, Ui::kBlockquoteSequence); + psBlockquote->setShortcutContext(Qt::WidgetShortcut); + psMonospace = edit->addAction( tr::lng_menu_formatting_monospace(tr::now), [] { @@ -315,6 +340,8 @@ void MainWindow::createGlobalMenu() { }, Ui::kMonospaceSequence); + psMonospace->setShortcutContext(Qt::WidgetShortcut); + psClearFormat = edit->addAction( tr::lng_menu_formatting_clear(tr::now), [] { @@ -324,6 +351,8 @@ void MainWindow::createGlobalMenu() { }, Ui::kClearFormatSequence); + psClearFormat->setShortcutContext(Qt::WidgetShortcut); + edit->addSeparator(); psSelectAll = edit->addAction( @@ -331,6 +360,8 @@ void MainWindow::createGlobalMenu() { [] { SendKeySequence(Qt::Key_A, Qt::ControlModifier); }, QKeySequence::SelectAll); + psSelectAll->setShortcutContext(Qt::WidgetShortcut); + edit->addSeparator(); auto prefs = edit->addAction( @@ -343,6 +374,7 @@ void MainWindow::createGlobalMenu() { QKeySequence(Qt::ControlModifier | Qt::Key_Comma)); prefs->setMenuRole(QAction::PreferencesRole); + prefs->setShortcutContext(Qt::WidgetShortcut); auto tools = psMainMenu->addMenu(tr::lng_linux_menu_tools(tr::now)); diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index 43ba894b4..f4e49f798 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -158,15 +158,19 @@ private: namespace Platform { namespace { -void SendKeySequence(Qt::Key key, Qt::KeyboardModifiers modifiers = Qt::NoModifier) { - const auto focused = static_cast(QApplication::focusWidget()); +void SendKeySequence( + Qt::Key key, + Qt::KeyboardModifiers modifiers = Qt::NoModifier) { + const auto focused = QApplication::focusWidget(); if (qobject_cast(focused) || qobject_cast(focused) || dynamic_cast(focused)) { - QKeyEvent pressEvent(QEvent::KeyPress, key, modifiers); - focused->event(&pressEvent); - QKeyEvent releaseEvent(QEvent::KeyRelease, key, modifiers); - focused->event(&releaseEvent); + QApplication::postEvent( + focused, + new QKeyEvent(QEvent::KeyPress, key, modifiers)); + QApplication::postEvent( + focused, + new QKeyEvent(QEvent::KeyRelease, key, modifiers)); } } @@ -376,12 +380,13 @@ void MainWindow::createGlobalMenu() { ensureWindowShown(); controller().showSettings(); }; - main->addAction( + auto prefs = main->addAction( tr::lng_mac_menu_preferences(tr::now), this, std::move(callback), - QKeySequence(Qt::ControlModifier | Qt::Key_Comma)) - ->setMenuRole(QAction::PreferencesRole); + QKeySequence(Qt::ControlModifier | Qt::Key_Comma)); + prefs->setMenuRole(QAction::PreferencesRole); + prefs->setShortcutContext(Qt::WidgetShortcut); } QMenu *file = psMainMenu.addMenu(tr::lng_mac_menu_file(tr::now)); @@ -402,6 +407,7 @@ void MainWindow::createGlobalMenu() { this, [] { SendKeySequence(Qt::Key_Z, Qt::ControlModifier); }, QKeySequence::Undo); + psUndo->setShortcutContext(Qt::WidgetShortcut); psRedo = edit->addAction( tr::lng_mac_menu_redo(tr::now), this, @@ -411,27 +417,32 @@ void MainWindow::createGlobalMenu() { Qt::ControlModifier | Qt::ShiftModifier); }, QKeySequence::Redo); + psRedo->setShortcutContext(Qt::WidgetShortcut); edit->addSeparator(); psCut = edit->addAction( tr::lng_mac_menu_cut(tr::now), this, [] { SendKeySequence(Qt::Key_X, Qt::ControlModifier); }, QKeySequence::Cut); + psCut->setShortcutContext(Qt::WidgetShortcut); psCopy = edit->addAction( tr::lng_mac_menu_copy(tr::now), this, [] { SendKeySequence(Qt::Key_C, Qt::ControlModifier); }, QKeySequence::Copy); + psCopy->setShortcutContext(Qt::WidgetShortcut); psPaste = edit->addAction( tr::lng_mac_menu_paste(tr::now), this, [] { SendKeySequence(Qt::Key_V, Qt::ControlModifier); }, QKeySequence::Paste); + psPaste->setShortcutContext(Qt::WidgetShortcut); psDelete = edit->addAction( tr::lng_mac_menu_delete(tr::now), this, [] { SendKeySequence(Qt::Key_Delete); }, QKeySequence(Qt::ControlModifier | Qt::Key_Backspace)); + psDelete->setShortcutContext(Qt::WidgetShortcut); edit->addSeparator(); psBold = edit->addAction( @@ -439,16 +450,19 @@ void MainWindow::createGlobalMenu() { this, [] { SendKeySequence(Qt::Key_B, Qt::ControlModifier); }, QKeySequence::Bold); + psBold->setShortcutContext(Qt::WidgetShortcut); psItalic = edit->addAction( tr::lng_menu_formatting_italic(tr::now), this, [] { SendKeySequence(Qt::Key_I, Qt::ControlModifier); }, QKeySequence::Italic); + psItalic->setShortcutContext(Qt::WidgetShortcut); psUnderline = edit->addAction( tr::lng_menu_formatting_underline(tr::now), this, [] { SendKeySequence(Qt::Key_U, Qt::ControlModifier); }, QKeySequence::Underline); + psUnderline->setShortcutContext(Qt::WidgetShortcut); psStrikeOut = edit->addAction( tr::lng_menu_formatting_strike_out(tr::now), this, @@ -458,6 +472,7 @@ void MainWindow::createGlobalMenu() { Qt::ControlModifier | Qt::ShiftModifier); }, Ui::kStrikeOutSequence); + psStrikeOut->setShortcutContext(Qt::WidgetShortcut); psBlockquote = edit->addAction( tr::lng_menu_formatting_blockquote(tr::now), this, @@ -467,6 +482,7 @@ void MainWindow::createGlobalMenu() { Qt::ControlModifier | Qt::ShiftModifier); }, Ui::kBlockquoteSequence); + psBlockquote->setShortcutContext(Qt::WidgetShortcut); psMonospace = edit->addAction( tr::lng_menu_formatting_monospace(tr::now), this, @@ -476,6 +492,7 @@ void MainWindow::createGlobalMenu() { Qt::ControlModifier | Qt::ShiftModifier); }, Ui::kMonospaceSequence); + psMonospace->setShortcutContext(Qt::WidgetShortcut); psClearFormat = edit->addAction( tr::lng_menu_formatting_clear(tr::now), this, @@ -485,6 +502,7 @@ void MainWindow::createGlobalMenu() { Qt::ControlModifier | Qt::ShiftModifier); }, Ui::kClearFormatSequence); + psClearFormat->setShortcutContext(Qt::WidgetShortcut); edit->addSeparator(); psSelectAll = edit->addAction( @@ -492,13 +510,15 @@ void MainWindow::createGlobalMenu() { this, [] { SendKeySequence(Qt::Key_A, Qt::ControlModifier); }, QKeySequence::SelectAll); + psSelectAll->setShortcutContext(Qt::WidgetShortcut); edit->addSeparator(); edit->addAction( tr::lng_mac_menu_emoji_and_symbols(tr::now).replace('&', "&&"), this, [] { [NSApp orderFrontCharacterPalette:nil]; }, - QKeySequence(Qt::MetaModifier | Qt::ControlModifier | Qt::Key_Space)); + QKeySequence(Qt::MetaModifier | Qt::ControlModifier | Qt::Key_Space) + )->setShortcutContext(Qt::WidgetShortcut); QMenu *window = psMainMenu.addMenu(tr::lng_mac_menu_window(tr::now)); psContacts = window->addAction(tr::lng_mac_menu_contacts(tr::now)); From d993386756802618d4d09b17f72743ba100d5dde Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 30 Jun 2024 10:31:39 +0400 Subject: [PATCH 050/142] Fix debug-only openssl build on Windows --- Telegram/build/prepare/prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index ccc7807dc..60d60ea47 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -616,11 +616,11 @@ release: move out.dbg\\ossl_static.pdb out.dbg\\ossl_static jom clean move out.dbg\\ossl_static out.dbg\\ossl_static.pdb -win32: +win32_release: perl Configure no-shared no-tests VC-WIN32 /FS -win64: +win64_release: perl Configure no-shared no-tests VC-WIN64A /FS -win: +win_release: jom -j%NUMBER_OF_PROCESSORS% mkdir out move libcrypto.lib out From fbbcc21198fb73138d3ac1dcd782526fa803ba48 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 14 Jun 2024 09:38:35 +0400 Subject: [PATCH 051/142] Update API scheme to layer 182. --- Telegram/SourceFiles/api/api_credits.cpp | 5 ++- Telegram/SourceFiles/apiwrap.cpp | 3 +- .../SourceFiles/core/click_handler_types.cpp | 13 +++++-- .../SourceFiles/core/click_handler_types.h | 1 + Telegram/SourceFiles/data/data_story.cpp | 37 ++++++++++++++++++- Telegram/SourceFiles/data/data_story.h | 12 ++++++ .../stories/media_stories_controller.cpp | 21 ++++++++++- .../media/stories/media_stories_controller.h | 1 + .../media/stories/media_stories_view.cpp | 4 ++ .../media/stories/media_stories_view.h | 6 +++ .../media/view/media_view_overlay_widget.cpp | 2 + Telegram/SourceFiles/mtproto/scheme/api.tl | 32 ++++++++++++---- Telegram/cmake/td_scheme.cmake | 2 +- 13 files changed, 123 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 44d274e58..dcbaac4a6 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -20,6 +20,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Api { namespace { +constexpr auto kTransactionsLimit = 100; + [[nodiscard]] Data::CreditsHistoryEntry HistoryFromTL( const MTPStarsTransaction &tl, not_null peer) { @@ -152,7 +154,8 @@ void CreditsHistory::request( _requestId = _api.request(MTPpayments_GetStarsTransactions( MTP_flags(_flags), _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input, - MTP_string(token) + MTP_string(token), + MTP_int(kTransactionsLimit) )).done([=](const MTPpayments_StarsStatus &result) { _requestId = 0; done(StatusFromTL(result, _peer)); diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index ba4400684..62f2cfbd4 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -2165,7 +2165,8 @@ void ApiWrap::saveDraftsToCloud() { entities, Data::WebPageForMTP( cloudDraft->webpage, - textWithTags.text.isEmpty()) + textWithTags.text.isEmpty()), + MTP_long(0) // effect )).done([=](const MTPBool &result, const MTP::Response &response) { const auto requestId = response.requestId; history->finishSavingCloudDraft( diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index 1c24dc510..9b03e0b74 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_controller.h" #include "window/window_session_controller.h" #include "window/window_session_controller_link_info.h" +#include "styles/style_calls.h" // groupCallBoxLabel #include "styles/style_layers.h" namespace { @@ -121,7 +122,10 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) { } else { const auto parsedUrl = QUrl::fromUserInput(url); if (UrlRequiresConfirmation(parsedUrl) && !base::IsCtrlPressed()) { - Core::App().hideMediaView(); + const auto my = context.value(); + if (!my.show) { + Core::App().hideMediaView(); + } const auto displayed = parsedUrl.isValid() ? parsedUrl.toDisplayString() : url; @@ -130,7 +134,6 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) { : parsedUrl.isValid() ? QString::fromUtf8(parsedUrl.toEncoded()) : ShowEncoded(displayed); - const auto my = context.value(); const auto controller = my.sessionWindow.get(); const auto use = controller ? &controller->window() @@ -140,8 +143,11 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) { .text = (tr::lng_open_this_link(tr::now)), .confirmed = [=](Fn hide) { hide(); open(); }, .confirmText = tr::lng_open_link(), + .labelStyle = my.dark ? &st::groupCallBoxLabel : nullptr, }); - const auto &st = st::boxLabel; + const auto &st = my.dark + ? st::groupCallBoxLabel + : st::boxLabel; box->addSkip(st.style.lineHeight - st::boxPadding.bottom()); const auto url = box->addRow( object_ptr(box, displayUrl, st)); @@ -190,6 +196,7 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const { _bot->name()), .confirmed = callback, .confirmText = tr::lng_allow_bot(), + .labelStyle = my.dark ? &st::groupCallBoxLabel : nullptr, })); } } diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h index 20879a0ab..a63594195 100644 --- a/Telegram/SourceFiles/core/click_handler_types.h +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -47,6 +47,7 @@ struct ClickHandlerContext { bool skipBotAutoLogin = false; bool botStartAutoSubmit = false; bool ignoreIv = false; + bool dark = false; // Is filled from peer info. PeerData *peer = nullptr; }; diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index 9db62b958..38ea28aae 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -82,6 +82,7 @@ using UpdateFlag = StoryUpdate::Flag; }); }, [&](const MTPDmediaAreaSuggestedReaction &data) { }, [&](const MTPDmediaAreaChannelPost &data) { + }, [&](const MTPDmediaAreaUrl &data) { }, [&](const MTPDinputMediaAreaChannelPost &data) { LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); }, [&](const MTPDinputMediaAreaVenue &data) { @@ -103,6 +104,7 @@ using UpdateFlag = StoryUpdate::Flag; .dark = data.is_dark(), }); }, [&](const MTPDmediaAreaChannelPost &data) { + }, [&](const MTPDmediaAreaUrl &data) { }, [&](const MTPDinputMediaAreaChannelPost &data) { LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); }, [&](const MTPDinputMediaAreaVenue &data) { @@ -124,6 +126,27 @@ using UpdateFlag = StoryUpdate::Flag; peerFromChannel(data.vchannel_id()), data.vmsg_id().v), }); + }, [&](const MTPDmediaAreaUrl &data) { + }, [&](const MTPDinputMediaAreaChannelPost &data) { + LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); + }, [&](const MTPDinputMediaAreaVenue &data) { + LOG(("API Error: Unexpected inputMediaAreaVenue from API.")); + }); + return result; +} + +[[nodiscard]] auto ParseUrlArea(const MTPMediaArea &area) +-> std::optional { + auto result = std::optional(); + area.match([&](const MTPDmediaAreaVenue &data) { + }, [&](const MTPDmediaAreaGeoPoint &data) { + }, [&](const MTPDmediaAreaSuggestedReaction &data) { + }, [&](const MTPDmediaAreaChannelPost &data) { + }, [&](const MTPDmediaAreaUrl &data) { + result.emplace(UrlArea{ + .area = ParseArea(data.vcoordinates()), + .url = qs(data.vurl()), + }); }, [&](const MTPDinputMediaAreaChannelPost &data) { LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); }, [&](const MTPDinputMediaAreaVenue &data) { @@ -662,6 +685,10 @@ const std::vector &Story::channelPosts() const { return _channelPosts; } +const std::vector &Story::urlAreas() const { + return _urlAreas; +} + void Story::applyChanges( StoryMedia media, const MTPDstoryItem &data, @@ -765,6 +792,7 @@ void Story::applyFields( auto locations = std::vector(); auto suggestedReactions = std::vector(); auto channelPosts = std::vector(); + auto urlAreas = std::vector(); if (const auto areas = data.vmedia_areas()) { for (const auto &area : areas->v) { if (const auto location = ParseLocation(area)) { @@ -778,6 +806,8 @@ void Story::applyFields( suggestedReactions.push_back(*reaction); } else if (auto post = ParseChannelPost(area)) { channelPosts.push_back(*post); + } else if (auto url = ParseUrlArea(area)) { + urlAreas.push_back(*url); } } } @@ -790,6 +820,7 @@ void Story::applyFields( const auto suggestedReactionsChanged = (_suggestedReactions != suggestedReactions); const auto channelPostsChanged = (_channelPosts != channelPosts); + const auto urlAreasChanged = (_urlAreas != urlAreas); const auto reactionChanged = (_sentReactionId != reaction); _out = out; @@ -815,6 +846,9 @@ void Story::applyFields( if (channelPostsChanged) { _channelPosts = std::move(channelPosts); } + if (urlAreasChanged) { + _urlAreas = std::move(urlAreas); + } if (reactionChanged) { _sentReactionId = reaction; } @@ -824,7 +858,8 @@ void Story::applyFields( || captionChanged || mediaChanged || locationsChanged - || channelPostsChanged; + || channelPostsChanged + || urlAreasChanged; const auto reactionsChanged = reactionChanged || suggestedReactionsChanged; if (!initial && (changed || reactionsChanged)) { diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index 512f0ed38..b318ce9fe 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -122,6 +122,15 @@ struct ChannelPost { const ChannelPost &) = default; }; +struct UrlArea { + StoryArea area; + QString url; + + friend inline bool operator==( + const UrlArea &, + const UrlArea &) = default; +}; + class Story final { public: Story( @@ -197,6 +206,8 @@ public: -> const std::vector &; [[nodiscard]] auto channelPosts() const -> const std::vector &; + [[nodiscard]] auto urlAreas() const + -> const std::vector &; void applyChanges( StoryMedia media, @@ -247,6 +258,7 @@ private: std::vector _locations; std::vector _suggestedReactions; std::vector _channelPosts; + std::vector _urlAreas; StoryViews _views; StoryViews _channelReactions; const TimeId _date = 0; diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 45e17f33f..c5fd13992 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/prepare_short_info_box.h" #include "chat_helpers/compose/compose_show.h" #include "core/application.h" +#include "core/click_handler_types.h" #include "core/core_settings.h" #include "core/update_checker.h" #include "data/data_changes.h" @@ -1046,6 +1047,9 @@ void Controller::updateAreas(Data::Story *story) { const auto &channelPosts = story ? story->channelPosts() : std::vector(); + const auto &urlAreas = story + ? story->urlAreas() + : std::vector(); if (_locations != locations) { _locations = locations; _areas.clear(); @@ -1054,6 +1058,10 @@ void Controller::updateAreas(Data::Story *story) { _channelPosts = channelPosts; _areas.clear(); } + if (_urlAreas != urlAreas) { + _urlAreas = urlAreas; + _areas.clear(); + } const auto reactionsCount = int(suggestedReactions.size()); if (_suggestedReactions.size() == reactionsCount && !_areas.empty()) { for (auto i = 0; i != reactionsCount; ++i) { @@ -1197,13 +1205,15 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const { if (!layout || (_locations.empty() && _suggestedReactions.empty() - && _channelPosts.empty())) { + && _channelPosts.empty() + && _urlAreas.empty())) { return nullptr; } else if (_areas.empty()) { const auto now = story(); _areas.reserve(_locations.size() + _suggestedReactions.size() - + _channelPosts.size()); + + _channelPosts.size() + + _urlAreas.size()); for (const auto &location : _locations) { _areas.push_back({ .original = location.area.geometry, @@ -1244,6 +1254,13 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const { }); } } + for (const auto &url : _urlAreas) { + _areas.push_back({ + .original = url.area.geometry, + .rotation = url.area.rotation, + .handler = std::make_shared(url.url), + }); + } rebuildActiveAreas(*layout); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 776667f73..3d486fa87 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -302,6 +302,7 @@ private: std::vector _locations; std::vector _suggestedReactions; std::vector _channelPosts; + std::vector _urlAreas; mutable std::vector _areas; std::vector _cachedSourcesList; diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_view.cpp index c8f5ffe94..73bd57e93 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_view.cpp @@ -170,6 +170,10 @@ void View::showFullCaption() { _controller->showFullCaption(); } +std::shared_ptr View::uiShow() const { + return _controller->uiShow(); +} + rpl::lifetime &View::lifetime() { return _controller->lifetime(); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.h b/Telegram/SourceFiles/media/stories/media_stories_view.h index da71d80d1..9436841d3 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.h +++ b/Telegram/SourceFiles/media/stories/media_stories_view.h @@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class ClickHandlerHost; +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + namespace Data { class Story; struct StoriesContext; @@ -125,6 +129,8 @@ public: not_null menu, QPoint desiredPosition); + [[nodiscard]] std::shared_ptr uiShow() const; + [[nodiscard]] rpl::lifetime &lifetime(); private: diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 297c464ff..2d2da578f 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -5840,6 +5840,8 @@ void OverlayWidget::handleMouseRelease( QVariant::fromValue(ClickHandlerContext{ .itemId = _message ? _message->fullId() : FullMsgId(), .sessionWindow = base::make_weak(findWindow()), + .show = _stories ? _stories->uiShow() : nullptr, + .dark = true, }) }); return; diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index f62e07bf6..5d35cc578 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -415,6 +415,8 @@ updateBotDeleteBusinessMessage#a02a982e connection_id:string peer:Peer messages: updateNewStoryReaction#1824e40b story_id:int peer:Peer reaction:Reaction = Update; updateBroadcastRevenueTransactions#dfd961f5 peer:Peer balances:BroadcastRevenueBalances = Update; updateStarsBalance#fb85198 balance:long = Update; +updateBusinessBotCallbackQuery#1ea2fda7 flags:# query_id:long user_id:long connection_id:string message:Message reply_to_message:flags.2?Message chat_instance:long data:flags.0?bytes = Update; +updateStarsRevenueStatus#a584b019 peer:Peer status:StarsRevenueStatus = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -792,7 +794,7 @@ contacts.topPeers#70b772a8 categories:Vector chats:Vector< contacts.topPeersDisabled#b52c939d = contacts.TopPeers; draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage; -draftMessage#3fccf7ef flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo message:string entities:flags.3?Vector media:flags.5?InputMedia date:int = DraftMessage; +draftMessage#2d65321f flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo message:string entities:flags.3?Vector media:flags.5?InputMedia date:int effect:flags.7?long = DraftMessage; messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers; messages.featuredStickers#be382906 flags:# premium:flags.0?true hash:long count:int sets:Vector unread:Vector = messages.FeaturedStickers; @@ -1603,14 +1605,15 @@ exportedStoryLink#3fc9053b link:string = ExportedStoryLink; storiesStealthMode#712e27fd flags:# active_until_date:flags.0?int cooldown_until_date:flags.1?int = StoriesStealthMode; -mediaAreaCoordinates#3d1ea4e x:double y:double w:double h:double rotation:double = MediaAreaCoordinates; +mediaAreaCoordinates#cfc9e002 flags:# x:double y:double w:double h:double rotation:double radius:flags.0?double = MediaAreaCoordinates; mediaAreaVenue#be82db9c coordinates:MediaAreaCoordinates geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MediaArea; inputMediaAreaVenue#b282217f coordinates:MediaAreaCoordinates query_id:long result_id:string = MediaArea; -mediaAreaGeoPoint#df8b3b22 coordinates:MediaAreaCoordinates geo:GeoPoint = MediaArea; +mediaAreaGeoPoint#cad5452d flags:# coordinates:MediaAreaCoordinates geo:GeoPoint address:flags.0?GeoPointAddress = MediaArea; mediaAreaSuggestedReaction#14455871 flags:# dark:flags.0?true flipped:flags.1?true coordinates:MediaAreaCoordinates reaction:Reaction = MediaArea; mediaAreaChannelPost#770416af coordinates:MediaAreaCoordinates channel_id:long msg_id:int = MediaArea; inputMediaAreaChannelPost#2271f2bf coordinates:MediaAreaCoordinates channel:InputChannel msg_id:int = MediaArea; +mediaAreaUrl#37381085 coordinates:MediaAreaCoordinates url:string = MediaArea; peerStories#9a35e999 flags:# peer:Peer max_read_id:flags.0?int stories:Vector = PeerStories; @@ -1803,10 +1806,22 @@ starsTransactionPeer#d80da15d peer:Peer = StarsTransactionPeer; starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; -starsTransaction#cc7079b2 flags:# refund:flags.3?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument = StarsTransaction; +starsTransaction#aa00c898 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string = StarsTransaction; payments.starsStatus#8cf4ee60 flags:# balance:long history:Vector next_offset:flags.0?string chats:Vector users:Vector = payments.StarsStatus; +foundStory#e87acbc0 peer:Peer story:StoryItem = FoundStory; + +stories.foundStories#e2de7737 flags:# count:int stories:Vector next_offset:flags.0?string chats:Vector users:Vector = stories.FoundStories; + +geoPointAddress#de4c5d93 flags:# country_iso2:string state:flags.0?string city:flags.1?string street:flags.2?string = GeoPointAddress; + +starsRevenueStatus#79342946 flags:# withdrawal_enabled:flags.0?true current_balance:long available_balance:long overall_revenue:long next_withdrawal_at:flags.1?int = StarsRevenueStatus; + +payments.starsRevenueStats#c92bb73b revenue_graph:StatsGraph status:StarsRevenueStatus usd_rate:double = payments.StarsRevenueStats; + +payments.starsRevenueWithdrawalUrl#1dab80b7 url:string = payments.StarsRevenueWithdrawalUrl; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -2050,7 +2065,7 @@ messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true invert_me messages.getBotCallbackAnswer#9342ca07 flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes password:flags.2?InputCheckPasswordSRP = messages.BotCallbackAnswer; messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool; messages.getPeerDialogs#e470bcfd peers:Vector = messages.PeerDialogs; -messages.saveDraft#7ff3b806 flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo peer:InputPeer message:string entities:flags.3?Vector media:flags.5?InputMedia = Bool; +messages.saveDraft#d372c5ce flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo peer:InputPeer message:string entities:flags.3?Vector media:flags.5?InputMedia effect:flags.7?long = Bool; messages.getAllDrafts#6a3f8d65 = Updates; messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers; messages.readFeaturedStickers#5b118126 id:Vector = Bool; @@ -2349,9 +2364,11 @@ payments.getGiveawayInfo#f4239425 peer:InputPeer msg_id:int = payments.GiveawayI payments.launchPrepaidGiveaway#5ff58f20 peer:InputPeer giveaway_id:long purpose:InputStorePaymentPurpose = Updates; payments.getStarsTopupOptions#c00ec7d3 = Vector; payments.getStarsStatus#104fcfa7 peer:InputPeer = payments.StarsStatus; -payments.getStarsTransactions#673ac2f9 flags:# inbound:flags.0?true outbound:flags.1?true peer:InputPeer offset:string = payments.StarsStatus; +payments.getStarsTransactions#97938d5a flags:# inbound:flags.0?true outbound:flags.1?true ascending:flags.2?true peer:InputPeer offset:string limit:int = payments.StarsStatus; payments.sendStarsForm#2bb731d flags:# 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; 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; @@ -2453,6 +2470,7 @@ stories.getChatsToSend#a56a8b60 = messages.Chats; stories.togglePeerStoriesHidden#bd0415c4 peer:InputPeer hidden:Bool = Bool; stories.getStoryReactionsList#b9b2881f flags:# forwards_first:flags.2?true peer:InputPeer id:int reaction:flags.0?Reaction offset:flags.1?string limit:int = stories.StoryReactionsList; stories.togglePinnedToTop#b297e9b peer:InputPeer id:Vector = Bool; +stories.searchPosts#6cea116a flags:# hashtag:flags.0?string area:flags.1?MediaArea offset:string limit:int = stories.FoundStories; premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:string limit:int = premium.BoostsList; premium.getMyBoosts#be77b4a = premium.MyBoosts; @@ -2470,4 +2488,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo; -// LAYER 181 +// LAYER 182 diff --git a/Telegram/cmake/td_scheme.cmake b/Telegram/cmake/td_scheme.cmake index 3cf326489..10f2c57c4 100644 --- a/Telegram/cmake/td_scheme.cmake +++ b/Telegram/cmake/td_scheme.cmake @@ -34,7 +34,7 @@ PUBLIC desktop-app::lib_tl ) -if (WIN32 AND NOT build_win64 AND NOT build_winarm AND QT_VERSION GREATER 6) +if (WIN32 AND NOT build_win64 AND NOT build_winarm) target_compile_options(td_scheme PRIVATE /bigobj # scheme.cpp has too many sections. From 4a60c5766174bd576394ee29d34f9fd801422f70 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 19 Jun 2024 04:53:33 +0300 Subject: [PATCH 052/142] Added dummy files of bot earn section. --- Telegram/CMakeLists.txt | 4 + Telegram/Resources/langs/lang.strings | 2 + .../info/bot/earn/info_earn_inner_widget.cpp | 69 ++++++++++ .../info/bot/earn/info_earn_inner_widget.h | 69 ++++++++++ .../info/bot/earn/info_earn_widget.cpp | 125 ++++++++++++++++++ .../info/bot/earn/info_earn_widget.h | 68 ++++++++++ Telegram/SourceFiles/info/info_controller.h | 1 + 7 files changed, 338 insertions(+) create mode 100644 Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp create mode 100644 Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h create mode 100644 Telegram/SourceFiles/info/bot/earn/info_earn_widget.cpp create mode 100644 Telegram/SourceFiles/info/bot/earn/info_earn_widget.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 6c4bce205..39dc186cc 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -887,6 +887,10 @@ PRIVATE history/history_view_highlight_manager.h history/history_widget.cpp history/history_widget.h + info/bot/earn/info_earn_inner_widget.cpp + info/bot/earn/info_earn_inner_widget.h + info/bot/earn/info_earn_widget.cpp + info/bot/earn/info_earn_widget.h info/channel_statistics/boosts/create_giveaway_box.cpp info/channel_statistics/boosts/create_giveaway_box.h info/channel_statistics/boosts/giveaway/giveaway_list_controllers.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 7d7ff8f2d..ce2f75f76 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -5186,6 +5186,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_channel_earn_chart_overriden_detail_currency" = "Revenue in TON"; "lng_channel_earn_chart_overriden_detail_usd" = "Revenue in USD"; +"lng_bot_earn_title" = "Stars Balance"; + "lng_contact_add" = "Add"; "lng_contact_send_message" = "message"; diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp new file mode 100644 index 000000000..04c134a07 --- /dev/null +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp @@ -0,0 +1,69 @@ +/* +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 "info/bot/earn/info_earn_inner_widget.h" + +#include "data/data_user.h" +#include "info/bot/earn/info_earn_widget.h" +#include "info/info_controller.h" + +namespace Info::BotEarn { +namespace { +} // namespace + +InnerWidget::InnerWidget( + QWidget *parent, + not_null controller, + not_null peer) +: VerticalLayout(parent) +, _controller(controller) +, _peer(peer) +, _show(controller->uiShow()) { +} + +void InnerWidget::load() { +} + +void InnerWidget::fill() { +} + +void InnerWidget::saveState(not_null memento) { + memento->setState(base::take(_state)); +} + +void InnerWidget::restoreState(not_null memento) { + _state = memento->state(); + if (_state) { + fill(); + } else { + load(); + } + Ui::RpWidget::resizeToWidth(width()); +} + +rpl::producer InnerWidget::scrollToRequests() const { + return _scrollToRequests.events(); +} + +auto InnerWidget::showRequests() const -> rpl::producer { + return _showRequests.events(); +} + +void InnerWidget::showFinished() { + _showFinished.fire({}); +} + +void InnerWidget::setInnerFocus() { + _focusRequested.fire({}); +} + +not_null InnerWidget::peer() const { + return _peer; +} + +} // namespace Info::BotEarn + diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h new file mode 100644 index 000000000..b121b3dc0 --- /dev/null +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h @@ -0,0 +1,69 @@ +/* +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 "data/data_channel_earn.h" +#include "ui/widgets/scroll_area.h" +#include "ui/wrap/vertical_layout.h" + +namespace Ui { +class Show; +} // namespace Ui + +namespace Info { +class Controller; +} // namespace Info + +namespace Info::BotEarn { + +class Memento; + +[[nodiscard]] QImage IconCurrency( + const style::FlatLabel &label, + const QColor &c); + +class InnerWidget final : public Ui::VerticalLayout { +public: + struct ShowRequest final { + }; + + InnerWidget( + QWidget *parent, + not_null controller, + not_null peer); + + [[nodiscard]] not_null peer() const; + + [[nodiscard]] rpl::producer scrollToRequests() const; + [[nodiscard]] rpl::producer showRequests() const; + + void showFinished(); + void setInnerFocus(); + + void saveState(not_null memento); + void restoreState(not_null memento); + +private: + void load(); + void fill(); + + not_null _controller; + not_null _peer; + std::shared_ptr _show; + + Data::EarnStatistics _state; + + rpl::event_stream _scrollToRequests; + rpl::event_stream _showRequests; + rpl::event_stream<> _showFinished; + rpl::event_stream<> _focusRequested; + rpl::event_stream _loaded; + +}; + +} // namespace Info::BotEarn diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_widget.cpp new file mode 100644 index 000000000..59ddb37aa --- /dev/null +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_widget.cpp @@ -0,0 +1,125 @@ +/* +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 "info/bot/earn/info_earn_widget.h" + +#include "info/bot/earn/info_earn_inner_widget.h" +#include "info/info_controller.h" +#include "info/info_memento.h" +#include "lang/lang_keys.h" + +namespace Info::BotEarn { + +Memento::Memento(not_null controller) +: ContentMemento(Info::Statistics::Tag{ + controller->statisticsPeer(), + {}, + {}, +}) { +} + +Memento::Memento(not_null peer) +: ContentMemento(Info::Statistics::Tag{ peer, {}, {} }) { +} + +Memento::~Memento() = default; + +Section Memento::section() const { + return Section(Section::Type::BotEarn); +} + +void Memento::setState(SavedState state) { + _state = std::move(state); +} + +Memento::SavedState Memento::state() { + return base::take(_state); +} + +object_ptr Memento::createWidget( + QWidget *parent, + not_null controller, + const QRect &geometry) { + auto result = object_ptr(parent, controller); + result->setInternalState(geometry, this); + return result; +} + +Widget::Widget( + QWidget *parent, + not_null controller) +: ContentWidget(parent, controller) +, _inner(setInnerWidget( + object_ptr( + this, + controller, + controller->statisticsPeer()))) { + _inner->showRequests( + ) | rpl::start_with_next([=](InnerWidget::ShowRequest request) { + }, _inner->lifetime()); + _inner->scrollToRequests( + ) | rpl::start_with_next([=](const Ui::ScrollToRequest &request) { + scrollTo(request); + }, _inner->lifetime()); +} + +not_null Widget::peer() const { + return _inner->peer(); +} + +bool Widget::showInternal(not_null memento) { + return (memento->statisticsPeer() == peer()); +} + +rpl::producer Widget::title() { + return tr::lng_bot_earn_title(); +} + +void Widget::setInternalState( + const QRect &geometry, + not_null memento) { + setGeometry(geometry); + Ui::SendPendingMoveResizeEvents(this); + restoreState(memento); +} + +rpl::producer Widget::desiredShadowVisibility() const { + return rpl::single(true); +} + +void Widget::showFinished() { + _inner->showFinished(); +} + +void Widget::setInnerFocus() { + _inner->setInnerFocus(); +} + +std::shared_ptr Widget::doCreateMemento() { + auto result = std::make_shared(controller()); + saveState(result.get()); + return result; +} + +void Widget::saveState(not_null memento) { + memento->setScrollTop(scrollTopSave()); + _inner->saveState(memento); +} + +void Widget::restoreState(not_null memento) { + _inner->restoreState(memento); + scrollTopRestore(memento->scrollTop()); +} + +std::shared_ptr Make(not_null peer) { + return std::make_shared( + std::vector>( + 1, + std::make_shared(peer))); +} + +} // namespace Info::BotEarn diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_widget.h b/Telegram/SourceFiles/info/bot/earn/info_earn_widget.h new file mode 100644 index 000000000..f66d8b39f --- /dev/null +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_widget.h @@ -0,0 +1,68 @@ +/* +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 "data/data_channel_earn.h" +#include "info/info_content_widget.h" + +namespace Info::BotEarn { + +class InnerWidget; + +class Memento final : public ContentMemento { +public: + Memento(not_null controller); + Memento(not_null peer); + ~Memento(); + + object_ptr createWidget( + QWidget *parent, + not_null controller, + const QRect &geometry) override; + + Section section() const override; + + using SavedState = Data::EarnStatistics; + + void setState(SavedState states); + [[nodiscard]] SavedState state(); + +private: + SavedState _state; + +}; + +class Widget final : public ContentWidget { +public: + Widget(QWidget *parent, not_null controller); + + bool showInternal(not_null memento) override; + rpl::producer title() override; + rpl::producer desiredShadowVisibility() const override; + void showFinished() override; + void setInnerFocus() override; + + [[nodiscard]] not_null peer() const; + + void setInternalState( + const QRect &geometry, + not_null memento); + +private: + void saveState(not_null memento); + void restoreState(not_null memento); + + std::shared_ptr doCreateMemento() override; + + const not_null _inner; + +}; + +[[nodiscard]] std::shared_ptr Make(not_null peer); + +} // namespace Info::BotEarn diff --git a/Telegram/SourceFiles/info/info_controller.h b/Telegram/SourceFiles/info/info_controller.h index 628d50bca..de0337d26 100644 --- a/Telegram/SourceFiles/info/info_controller.h +++ b/Telegram/SourceFiles/info/info_controller.h @@ -135,6 +135,7 @@ public: Statistics, Boosts, ChannelEarn, + BotEarn, }; using SettingsType = ::Settings::Type; using MediaType = Storage::SharedMediaType; From 71893f4ef791c5472941424251a4bfcbee5f0cad Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 19 Jun 2024 04:15:32 +0300 Subject: [PATCH 053/142] Added initial implementation of entry point to bot earn section. --- Telegram/Resources/langs/lang.strings | 1 + .../boxes/peers/edit_peer_info_box.cpp | 87 +++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index ce2f75f76..90cffa321 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1561,6 +1561,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_manage_peer_bot_public_link" = "Public Link"; "lng_manage_peer_bot_public_links" = "Public Links"; +"lng_manage_peer_bot_balance" = "Balance"; "lng_manage_peer_bot_edit_intro" = "Edit Intro"; "lng_manage_peer_bot_edit_commands" = "Edit Commands"; "lng_manage_peer_bot_edit_settings" = "Change Bot Settings"; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 99427aca0..5ecab2999 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_peer_info_box.h" #include "apiwrap.h" +#include "api/api_credits.h" #include "api/api_peer_photo.h" #include "api/api_user_names.h" #include "main/main_session.h" @@ -42,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_premium_limits.h" #include "data/data_user.h" #include "history/admin_log/history_admin_log_section.h" +#include "info/bot/earn/info_earn_widget.h" #include "info/channel_statistics/boosts/info_boosts_widget.h" #include "info/profile/info_profile_values.h" #include "info/info_memento.h" @@ -52,6 +54,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/boost_box.h" #include "ui/controls/emoji_button.h" #include "ui/controls/userpic_button.h" +#include "ui/effects/premium_top_bar.h" +#include "ui/rect.h" #include "ui/rp_widget.h" #include "ui/vertical_list.h" #include "ui/toast/toast.h" @@ -71,6 +75,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_boxes.h" #include "styles/style_info.h" +#include + namespace { constexpr auto kBotManagerUsername = "BotFather"_cs; @@ -343,6 +349,7 @@ private: void fillPendingRequestsButton(); void fillBotUsernamesButton(); + void fillBotBalanceButton(); void fillBotEditIntroButton(); void fillBotEditCommandsButton(); void fillBotEditSettingsButton(); @@ -1126,6 +1133,7 @@ void Controller::fillManageSection() { ::AddSkip(container, 0); fillBotUsernamesButton(); + fillBotBalanceButton(); fillBotEditIntroButton(); fillBotEditCommandsButton(); fillBotEditSettingsButton(); @@ -1536,6 +1544,85 @@ void Controller::fillBotUsernamesButton() { { &st::menuIconLinks }); } +void Controller::fillBotBalanceButton() { + Expects(_isBot); + + struct State final { + rpl::variable balance; + }; + + auto &lifetime = _controls.buttonsLayout->lifetime(); + const auto state = lifetime.make_state(); + + const auto wrap = _controls.buttonsLayout->add( + object_ptr>( + _controls.buttonsLayout, + EditPeerInfoBox::CreateButton( + _controls.buttonsLayout, + tr::lng_manage_peer_bot_balance(), + state->balance.value(), + [controller = _navigation->parentController(), peer = _peer] { + controller->showSection(Info::BotEarn::Make(peer)); + }, + st::manageGroupButton, + {}))); + wrap->toggle(false, anim::type::instant); + + const auto button = wrap->entity(); + { + const auto api = button->lifetime().make_state( + _peer); + api->request({}, [=](Data::CreditsStatusSlice data) { + if (data.balance) { + wrap->toggle(true, anim::type::normal); + } + state->balance = QString::number(data.balance); + }); + } + { + constexpr auto kSizeShift = 3; + constexpr auto kStrokeWidth = 5; + + const auto icon = Ui::CreateChild(button); + icon->resize(Size(st::menuIconLinks.width() - kSizeShift)); + + const auto bg = st::boxBg->c; + auto colorized = [&] { + auto f = QFile(Ui::Premium::Svg()); + if (!f.open(QIODevice::ReadOnly)) { + return QString(); + } + return QString::fromUtf8( + f.readAll()).replace(u"#fff"_q, u"#ffffff00"_q); + }(); + colorized.replace( + u"stroke=\"none\""_q, + u"stroke=\"%1\""_q.arg(st::menuIconColor->c.name())); + colorized.replace( + u"stroke-width=\"1\""_q, + u"stroke-width=\"%1\""_q.arg(kStrokeWidth)); + const auto svg = icon->lifetime().make_state( + colorized.toUtf8()); + svg->setViewBox(svg->viewBox() + Margins(kStrokeWidth)); + + const auto starSize = Size(icon->height()); + + icon->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(icon); + svg->render(&p, Rect(starSize)); + }, icon->lifetime()); + + button->sizeValue( + ) | rpl::start_with_next([=](const QSize &size) { + icon->moveToLeft( + button->st().iconLeft + kSizeShift / 2., + (size.height() - icon->height()) / 2); + }, icon->lifetime()); + } + +} + void Controller::fillBotEditIntroButton() { Expects(_isBot); From c5ba0fa705d5674e857a655400d1dfe106935e01 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 19 Jun 2024 05:05:46 +0300 Subject: [PATCH 054/142] Added initial structure for bot earn data. --- Telegram/SourceFiles/data/data_bot_earn.h | 31 +++++++++++++++++++ .../info/bot/earn/info_earn_inner_widget.h | 4 +-- .../info/bot/earn/info_earn_widget.h | 4 +-- Telegram/cmake/td_ui.cmake | 1 + 4 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 Telegram/SourceFiles/data/data_bot_earn.h diff --git a/Telegram/SourceFiles/data/data_bot_earn.h b/Telegram/SourceFiles/data/data_bot_earn.h new file mode 100644 index 000000000..e208ccf00 --- /dev/null +++ b/Telegram/SourceFiles/data/data_bot_earn.h @@ -0,0 +1,31 @@ +/* +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 "data/data_statistics_chart.h" + +#include + +namespace Data { + +using BotEarnInt = uint64; + +struct BotEarnStatistics final { + explicit operator bool() const { + return !!usdRate; + } + Data::StatisticalGraph revenueGraph; + BotEarnInt currentBalance = 0; + BotEarnInt availableBalance = 0; + BotEarnInt overallRevenue = 0; + float64 usdRate = 0.; + bool isWithdrawalEnabled = false; + QDateTime nextWithdrawalAt; +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h index b121b3dc0..f1d6d470c 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "data/data_channel_earn.h" +#include "data/data_bot_earn.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/vertical_layout.h" @@ -56,7 +56,7 @@ private: not_null _peer; std::shared_ptr _show; - Data::EarnStatistics _state; + Data::BotEarnStatistics _state; rpl::event_stream _scrollToRequests; rpl::event_stream _showRequests; diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_widget.h b/Telegram/SourceFiles/info/bot/earn/info_earn_widget.h index f66d8b39f..b201a32af 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_widget.h +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_widget.h @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "data/data_channel_earn.h" +#include "data/data_bot_earn.h" #include "info/info_content_widget.h" namespace Info::BotEarn { @@ -27,7 +27,7 @@ public: Section section() const override; - using SavedState = Data::EarnStatistics; + using SavedState = Data::BotEarnStatistics; void setState(SavedState states); [[nodiscard]] SavedState state(); diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 2b69b2df7..ca5cb6003 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -80,6 +80,7 @@ PRIVATE data/data_birthday.cpp data/data_birthday.h + data/data_bot_earn.h data/data_channel_earn.h data/data_credits.h data/data_statistics_chart.cpp From b5d5ff3cbbdbf2796327c7143197f7c652e51f82 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 19 Jun 2024 05:23:24 +0300 Subject: [PATCH 055/142] Improved naming of classes for channel earn. --- Telegram/SourceFiles/api/api_statistics.cpp | 8 ++++---- Telegram/SourceFiles/api/api_statistics.h | 4 ++-- .../channel_statistics/earn/info_earn_inner_widget.cpp | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp index ce1966623..70c2a6ae7 100644 --- a/Telegram/SourceFiles/api/api_statistics.cpp +++ b/Telegram/SourceFiles/api/api_statistics.cpp @@ -747,11 +747,11 @@ Data::BoostStatus Boosts::boostStatus() const { return _boostStatus; } -EarnStatistics::EarnStatistics(not_null channel) +ChannelEarnStatistics::ChannelEarnStatistics(not_null channel) : StatisticsRequestSender(channel) { } -rpl::producer EarnStatistics::request() { +rpl::producer ChannelEarnStatistics::request() { return [=](auto consumer) { auto lifetime = rpl::lifetime(); @@ -795,7 +795,7 @@ rpl::producer EarnStatistics::request() { }; } -void EarnStatistics::requestHistory( +void ChannelEarnStatistics::requestHistory( const Data::EarnHistorySlice::OffsetToken &token, Fn done) { if (_requestId) { @@ -865,7 +865,7 @@ void EarnStatistics::requestHistory( }).send(); } -Data::EarnStatistics EarnStatistics::data() const { +Data::EarnStatistics ChannelEarnStatistics::data() const { return _data; } diff --git a/Telegram/SourceFiles/api/api_statistics.h b/Telegram/SourceFiles/api/api_statistics.h index f18cba71d..eca157ff0 100644 --- a/Telegram/SourceFiles/api/api_statistics.h +++ b/Telegram/SourceFiles/api/api_statistics.h @@ -108,9 +108,9 @@ private: }; -class EarnStatistics final : public StatisticsRequestSender { +class ChannelEarnStatistics final : public StatisticsRequestSender { public: - explicit EarnStatistics(not_null channel); + explicit ChannelEarnStatistics(not_null channel); [[nodiscard]] rpl::producer request(); void requestHistory( diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp index a151443e6..6e13c31fc 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp @@ -256,7 +256,7 @@ InnerWidget::InnerWidget( } void InnerWidget::load() { - const auto api = lifetime().make_state( + const auto api = lifetime().make_state( _peer->asChannel()); Info::Statistics::FillLoading( @@ -996,7 +996,7 @@ void InnerWidget::fill() { ShowMoreState(not_null channel) : api(channel) { } - Api::EarnStatistics api; + Api::ChannelEarnStatistics api; bool loading = false; Data::EarnHistorySlice::OffsetToken token; rpl::variable showed = 0; From f23c9a61bc41d9806e5909a0bc00da963cde7d6a Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 19 Jun 2024 05:46:13 +0300 Subject: [PATCH 056/142] Moved out StatisticsRequestSender to separated file. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/api/api_statistics.cpp | 57 ------------ Telegram/SourceFiles/api/api_statistics.h | 31 +------ .../SourceFiles/api/api_statistics_sender.cpp | 86 +++++++++++++++++++ .../SourceFiles/api/api_statistics_sender.h | 58 +++++++++++++ Telegram/SourceFiles/data/data_session.cpp | 12 +-- Telegram/SourceFiles/data/data_session.h | 6 +- 7 files changed, 156 insertions(+), 96 deletions(-) create mode 100644 Telegram/SourceFiles/api/api_statistics_sender.cpp create mode 100644 Telegram/SourceFiles/api/api_statistics_sender.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 39dc186cc..b8fbab91a 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -164,6 +164,8 @@ PRIVATE api/api_single_message_search.h api/api_statistics.cpp api/api_statistics.h + api/api_statistics_sender.cpp + api/api_statistics_sender.h api/api_text_entities.cpp api/api_text_entities.h api/api_toggling_media.cpp diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp index 70c2a6ae7..a017571a9 100644 --- a/Telegram/SourceFiles/api/api_statistics.cpp +++ b/Telegram/SourceFiles/api/api_statistics.cpp @@ -20,8 +20,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Api { namespace { -constexpr auto kCheckRequestsTimer = 10 * crl::time(1000); - [[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL( const MTPStatsGraph &tl) { return tl.match([&](const MTPDstatsGraph &d) { @@ -223,61 +221,6 @@ Statistics::Statistics(not_null channel) : StatisticsRequestSender(channel) { } -StatisticsRequestSender::StatisticsRequestSender(not_null channel) -: _channel(channel) -, _api(&_channel->session().api().instance()) -, _timer([=] { checkRequests(); }) { -} - -StatisticsRequestSender::~StatisticsRequestSender() { - for (const auto &[dcId, ids] : _requests) { - for (const auto id : ids) { - _channel->session().api().unregisterStatsRequest(dcId, id); - } - } -} - -void StatisticsRequestSender::checkRequests() { - for (auto i = begin(_requests); i != end(_requests);) { - for (auto j = begin(i->second); j != end(i->second);) { - if (_api.pending(*j)) { - ++j; - } else { - _channel->session().api().unregisterStatsRequest( - i->first, - *j); - j = i->second.erase(j); - } - } - if (i->second.empty()) { - i = _requests.erase(i); - } else { - ++i; - } - } - if (_requests.empty()) { - _timer.cancel(); - } -} - -template -auto StatisticsRequestSender::makeRequest(Request &&request) { - const auto id = _api.allocateRequestId(); - const auto dcId = _channel->owner().statsDcId(_channel); - if (dcId) { - _channel->session().api().registerStatsRequest(dcId, id); - _requests[dcId].emplace(id); - if (!_timer.isActive()) { - _timer.callEach(kCheckRequestsTimer); - } - } - return std::move(_api.request( - std::forward(request) - ).toDC( - dcId ? MTP::ShiftDcId(dcId, MTP::kStatsDcShift) : 0 - ).overrideId(id)); -} - rpl::producer Statistics::request() { return [=](auto consumer) { auto lifetime = rpl::lifetime(); diff --git a/Telegram/SourceFiles/api/api_statistics.h b/Telegram/SourceFiles/api/api_statistics.h index eca157ff0..213ab9293 100644 --- a/Telegram/SourceFiles/api/api_statistics.h +++ b/Telegram/SourceFiles/api/api_statistics.h @@ -7,45 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "base/timer.h" +#include "api/api_statistics_sender.h" #include "data/data_boosts.h" #include "data/data_channel_earn.h" #include "data/data_statistics.h" -#include "mtproto/sender.h" class ChannelData; class PeerData; namespace Api { -class StatisticsRequestSender { -protected: - explicit StatisticsRequestSender(not_null channel); - ~StatisticsRequestSender(); - - template < - typename Request, - typename = std::enable_if_t>, - typename = typename Request::Unboxed> - [[nodiscard]] auto makeRequest(Request &&request); - - [[nodiscard]] MTP::Sender &api() { - return _api; - } - [[nodiscard]] not_null channel() { - return _channel; - } - -private: - void checkRequests(); - - const not_null _channel; - MTP::Sender _api; - base::Timer _timer; - base::flat_map> _requests; - -}; - class Statistics final : public StatisticsRequestSender { public: explicit Statistics(not_null channel); diff --git a/Telegram/SourceFiles/api/api_statistics_sender.cpp b/Telegram/SourceFiles/api/api_statistics_sender.cpp new file mode 100644 index 000000000..21d068e3d --- /dev/null +++ b/Telegram/SourceFiles/api/api_statistics_sender.cpp @@ -0,0 +1,86 @@ +/* +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 "api/api_statistics_sender.h" + +#include "apiwrap.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "main/main_session.h" + +namespace Api { + +StatisticsRequestSender::StatisticsRequestSender( + not_null peer) +: _peer(peer) +, _channel(peer->asChannel()) +, _user(peer->asUser()) +, _api(&_peer->session().api().instance()) +, _timer([=] { checkRequests(); }) { +} + +MTP::Sender &StatisticsRequestSender::api() { + return _api; +} + +not_null StatisticsRequestSender::channel() { + Expects(_channel); + return _channel; +} + +not_null StatisticsRequestSender::user() { + Expects(_user); + return _user; +} + +void StatisticsRequestSender::checkRequests() { + for (auto i = begin(_requests); i != end(_requests);) { + for (auto j = begin(i->second); j != end(i->second);) { + if (_api.pending(*j)) { + ++j; + } else { + _peer->session().api().unregisterStatsRequest( + i->first, + *j); + j = i->second.erase(j); + } + } + if (i->second.empty()) { + i = _requests.erase(i); + } else { + ++i; + } + } + if (_requests.empty()) { + _timer.cancel(); + } +} + +auto StatisticsRequestSender::ensureRequestIsRegistered() +-> StatisticsRequestSender::Registered { + const auto id = _api.allocateRequestId(); + const auto dcId = _peer->owner().statsDcId(_peer); + if (dcId) { + _peer->session().api().registerStatsRequest(dcId, id); + _requests[dcId].emplace(id); + if (!_timer.isActive()) { + constexpr auto kCheckRequestsTimer = 10 * crl::time(1000); + _timer.callEach(kCheckRequestsTimer); + } + } + return StatisticsRequestSender::Registered{ id, dcId }; +} + +StatisticsRequestSender::~StatisticsRequestSender() { + for (const auto &[dcId, ids] : _requests) { + for (const auto id : ids) { + _peer->session().api().unregisterStatsRequest(dcId, id); + } + } +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_statistics_sender.h b/Telegram/SourceFiles/api/api_statistics_sender.h new file mode 100644 index 000000000..2ad0b664d --- /dev/null +++ b/Telegram/SourceFiles/api/api_statistics_sender.h @@ -0,0 +1,58 @@ +/* +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 "base/timer.h" +#include "mtproto/sender.h" + +class ChannelData; +class PeerData; +class UserData; + +namespace Api { + +class StatisticsRequestSender { +protected: + explicit StatisticsRequestSender(not_null peer); + ~StatisticsRequestSender(); + + template < + typename Request, + typename = std::enable_if_t>, + typename = typename Request::Unboxed> + [[nodiscard]] auto makeRequest(Request &&request) { + const auto [id, dcId] = ensureRequestIsRegistered(); + return std::move(_api.request( + std::forward(request) + ).toDC( + dcId ? MTP::ShiftDcId(dcId, MTP::kStatsDcShift) : 0 + ).overrideId(id)); + } + + [[nodiscard]] MTP::Sender &api(); + [[nodiscard]] not_null channel(); + [[nodiscard]] not_null user(); + +private: + struct Registered final { + mtpRequestId id; + MTP::DcId dcId; + }; + [[nodiscard]] Registered ensureRequestIsRegistered(); + void checkRequests(); + + const not_null _peer; + ChannelData * const _channel; + UserData * const _user; + MTP::Sender _api; + base::Timer _timer; + base::flat_map> _requests; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index c11c2ef8a..b22126ebb 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -4683,16 +4683,16 @@ uint64 Session::wallpapersHash() const { return _wallpapersHash; } -MTP::DcId Session::statsDcId(not_null channel) { - const auto it = _channelStatsDcIds.find(channel); - return (it == end(_channelStatsDcIds)) ? MTP::DcId(0) : it->second; +MTP::DcId Session::statsDcId(not_null peer) { + const auto it = _peerStatsDcIds.find(peer); + return (it == end(_peerStatsDcIds)) ? MTP::DcId(0) : it->second; } void Session::applyStatsDcId( - not_null channel, + not_null peer, MTP::DcId dcId) { - if (dcId != channel->session().mainDcId()) { - _channelStatsDcIds[channel] = dcId; + if (dcId != peer->session().mainDcId()) { + _peerStatsDcIds[peer] = dcId; } } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 117823802..12c4cba3a 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -753,8 +753,8 @@ public: [[nodiscard]] auto peerDecorationsUpdated() const -> rpl::producer>; - void applyStatsDcId(not_null, MTP::DcId); - [[nodiscard]] MTP::DcId statsDcId(not_null); + void applyStatsDcId(not_null, MTP::DcId); + [[nodiscard]] MTP::DcId statsDcId(not_null); void viewTagsChanged( not_null view, @@ -1053,7 +1053,7 @@ private: base::flat_map, TimeId> _watchingForOffline; base::Timer _watchForOfflineTimer; - base::flat_map, MTP::DcId> _channelStatsDcIds; + base::flat_map, MTP::DcId> _peerStatsDcIds; rpl::event_stream _webViewResultSent; From cb65c50c19657de61452f7acb315349d7ea809df Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 19 Jun 2024 06:00:54 +0300 Subject: [PATCH 057/142] Moved out deserialization of MTP statistical graph to separated file. --- Telegram/CMakeLists.txt | 2 ++ Telegram/SourceFiles/api/api_statistics.cpp | 22 +----------- .../api/api_statistics_data_deserialize.cpp | 35 +++++++++++++++++++ .../api/api_statistics_data_deserialize.h | 19 ++++++++++ 4 files changed, 57 insertions(+), 21 deletions(-) create mode 100644 Telegram/SourceFiles/api/api_statistics_data_deserialize.cpp create mode 100644 Telegram/SourceFiles/api/api_statistics_data_deserialize.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index b8fbab91a..686bb93be 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -164,6 +164,8 @@ PRIVATE api/api_single_message_search.h api/api_statistics.cpp api/api_statistics.h + api/api_statistics_data_deserialize.cpp + api/api_statistics_data_deserialize.h api/api_statistics_sender.cpp api/api_statistics_sender.h api/api_text_entities.cpp diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp index a017571a9..869d0bf8a 100644 --- a/Telegram/SourceFiles/api/api_statistics.cpp +++ b/Telegram/SourceFiles/api/api_statistics.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "api/api_statistics.h" +#include "api/api_statistics_data_deserialize.h" #include "apiwrap.h" #include "base/unixtime.h" #include "data/data_channel.h" @@ -15,31 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_story.h" #include "history/history.h" #include "main/main_session.h" -#include "statistics/statistics_data_deserialize.h" namespace Api { namespace { -[[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL( - const MTPStatsGraph &tl) { - return tl.match([&](const MTPDstatsGraph &d) { - using namespace Statistic; - const auto zoomToken = d.vzoom_token().has_value() - ? qs(*d.vzoom_token()).toUtf8() - : QByteArray(); - return Data::StatisticalGraph{ - StatisticalChartFromJSON(qs(d.vjson().data().vdata()).toUtf8()), - zoomToken, - }; - }, [&](const MTPDstatsGraphAsync &data) { - return Data::StatisticalGraph{ - .zoomToken = qs(data.vtoken()).toUtf8(), - }; - }, [&](const MTPDstatsGraphError &data) { - return Data::StatisticalGraph{ .error = qs(data.verror()) }; - }); -} - [[nodiscard]] Data::StatisticalValue StatisticalValueFromTL( const MTPStatsAbsValueAndPrev &tl) { const auto current = tl.data().vcurrent().v; diff --git a/Telegram/SourceFiles/api/api_statistics_data_deserialize.cpp b/Telegram/SourceFiles/api/api_statistics_data_deserialize.cpp new file mode 100644 index 000000000..9ce75af78 --- /dev/null +++ b/Telegram/SourceFiles/api/api_statistics_data_deserialize.cpp @@ -0,0 +1,35 @@ +/* +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 "api/api_statistics_data_deserialize.h" + +#include "data/data_statistics_chart.h" +#include "statistics/statistics_data_deserialize.h" + +namespace Api { + +Data::StatisticalGraph StatisticalGraphFromTL(const MTPStatsGraph &tl) { + return tl.match([&](const MTPDstatsGraph &d) { + using namespace Statistic; + const auto zoomToken = d.vzoom_token().has_value() + ? qs(*d.vzoom_token()).toUtf8() + : QByteArray(); + return Data::StatisticalGraph{ + StatisticalChartFromJSON(qs(d.vjson().data().vdata()).toUtf8()), + zoomToken, + }; + }, [&](const MTPDstatsGraphAsync &data) { + return Data::StatisticalGraph{ + .zoomToken = qs(data.vtoken()).toUtf8(), + }; + }, [&](const MTPDstatsGraphError &data) { + return Data::StatisticalGraph{ .error = qs(data.verror()) }; + }); +} + + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_statistics_data_deserialize.h b/Telegram/SourceFiles/api/api_statistics_data_deserialize.h new file mode 100644 index 000000000..385b99d17 --- /dev/null +++ b/Telegram/SourceFiles/api/api_statistics_data_deserialize.h @@ -0,0 +1,19 @@ +/* +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 + +namespace Data { +struct StatisticalGraph; +} // namespace Data + +namespace Api { + +[[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL( + const MTPStatsGraph &tl); + +} // namespace Api From 049cde48ee6474d0bfc90c474d61c3f74183bc28 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 19 Jun 2024 06:17:16 +0300 Subject: [PATCH 058/142] Added initial api support of bot earn statistics. --- Telegram/SourceFiles/api/api_credits.cpp | 42 +++++++++++++++++++++++- Telegram/SourceFiles/api/api_credits.h | 18 ++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index dcbaac4a6..13cf45474 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -7,8 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "api/api_credits.h" -#include "apiwrap.h" +#include "api/api_statistics_data_deserialize.h" #include "api/api_updates.h" +#include "apiwrap.h" #include "base/unixtime.h" #include "data/data_peer.h" #include "data/data_photo.h" @@ -202,4 +203,43 @@ rpl::producer> PremiumPeerBot( }; } +BotEarnStatistics::BotEarnStatistics(not_null user) +: StatisticsRequestSender(user) { +} + +rpl::producer BotEarnStatistics::request() { + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + + makeRequest(MTPpayments_GetStarsRevenueStats( + MTP_flags(0), + user()->input + )).done([=](const MTPpayments_StarsRevenueStats &result) { + const auto &data = result.data(); + const auto &status = data.vstatus().data(); + _data = Data::BotEarnStatistics{ + .revenueGraph = StatisticalGraphFromTL(data.vrevenue_graph()), + .currentBalance = status.vcurrent_balance().v, + .availableBalance = status.vavailable_balance().v, + .overallRevenue = status.voverall_revenue().v, + .usdRate = data.vusd_rate().v, + .isWithdrawalEnabled = status.is_withdrawal_enabled(), + .nextWithdrawalAt = status.vnext_withdrawal_at() + ? base::unixtime::parse(status.vnext_withdrawal_at()->v) + : QDateTime(), + }; + + consumer.put_done(); + }).fail([=](const MTP::Error &error) { + consumer.put_error_copy(error.type()); + }).send(); + + return lifetime; + }; +} + +Data::BotEarnStatistics BotEarnStatistics::data() const { + return _data; +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h index 265e7b387..ae9ff5115 100644 --- a/Telegram/SourceFiles/api/api_credits.h +++ b/Telegram/SourceFiles/api/api_credits.h @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "api/api_statistics_sender.h" +#include "data/data_bot_earn.h" #include "data/data_credits.h" #include "mtproto/sender.h" @@ -14,6 +16,8 @@ namespace Main { class Session; } // namespace Main +class UserData; + namespace Api { class CreditsTopupOptions final { @@ -68,6 +72,20 @@ private: }; +class BotEarnStatistics final : public StatisticsRequestSender { +public: + explicit BotEarnStatistics(not_null); + + [[nodiscard]] rpl::producer request(); + [[nodiscard]] Data::BotEarnStatistics data() const; + +private: + Data::BotEarnStatistics _data; + + mtpRequestId _requestId = 0; + +}; + [[nodiscard]] rpl::producer> PremiumPeerBot( not_null session); From 811d75e3837c948588c14a0d0fa7aeac763502de Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 19 Jun 2024 07:40:17 +0300 Subject: [PATCH 059/142] Improved display of different inner currencies in statistical charts. --- .../SourceFiles/data/data_statistics_chart.h | 8 +++++++ .../statistics_data_deserialize.cpp | 23 +++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/data/data_statistics_chart.h b/Telegram/SourceFiles/data/data_statistics_chart.h index fba664d34..11782651e 100644 --- a/Telegram/SourceFiles/data/data_statistics_chart.h +++ b/Telegram/SourceFiles/data/data_statistics_chart.h @@ -11,6 +11,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { +enum class StatisticalCurrency { + None, + Ton, + Credits, +}; + struct StatisticalChart { StatisticalChart() = default; @@ -67,6 +73,8 @@ struct StatisticalChart { bool isFooterHidden = false; bool hasPercentages = false; bool weekFormat = false; + + StatisticalCurrency currency = StatisticalCurrency::None; float64 currencyRate = 0.; // View data. diff --git a/Telegram/SourceFiles/statistics/statistics_data_deserialize.cpp b/Telegram/SourceFiles/statistics/statistics_data_deserialize.cpp index 7f47a396a..4e3780529 100644 --- a/Telegram/SourceFiles/statistics/statistics_data_deserialize.cpp +++ b/Telegram/SourceFiles/statistics/statistics_data_deserialize.cpp @@ -8,8 +8,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "statistics/statistics_data_deserialize.h" #include "base/debug_log.h" +#include "data/data_channel_earn.h" // kEarnMultiplier. #include "data/data_statistics_chart.h" #include "statistics/statistics_types.h" +#include "ui/text/format_values.h" // kCreditsCurrency. #include #include @@ -40,6 +42,18 @@ Data::StatisticalChart StatisticalChartFromJSON(const QByteArray &json) { }) | ranges::to_vector; auto result = Data::StatisticalChart(); + + { + const auto tickFormatIt = root.constFind(u"yTickFormatter"_q); + if (tickFormatIt != root.constEnd()) { + const auto tickFormat = tickFormatIt->toString(); + if (tickFormat.contains(u"TON"_q)) { + result.currency = Data::StatisticalCurrency::Ton; + } else if (tickFormat.contains(Ui::kCreditsCurrency)) { + result.currency = Data::StatisticalCurrency::Credits; + } + } + } auto columnIdCount = 0; for (const auto &column : columns) { const auto array = column.toArray(); @@ -62,8 +76,13 @@ Data::StatisticalChart StatisticalChartFromJSON(const QByteArray &json) { line.isHiddenOnStart = ranges::contains(hiddenLines, columnId); line.y.resize(length); for (auto i = 0; i < length; i++) { - const auto value = ChartValue(base::SafeRound( - array.at(i + 1).toDouble())); + using Currency = Data::StatisticalCurrency; + const auto multiplier = (result.currency == Currency::Credits) + ? Data::kEarnMultiplier + : 1; + const auto value = ChartValue( + base::SafeRound(array.at(i + 1).toDouble())) + * multiplier; line.y[i] = value; if (value > line.maxValue) { line.maxValue = value; From bef216bc93f42a09964fab40ba79ae6819ac5822 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 19 Jun 2024 17:00:07 +0300 Subject: [PATCH 060/142] Moved out colorizing of svg star to td_ui. --- .../boxes/peers/edit_peer_info_box.cpp | 2 +- .../ui/effects/premium_graphics.cpp | 66 +++++++++++++++++++ .../SourceFiles/ui/effects/premium_graphics.h | 4 ++ .../ui/effects/premium_top_bar.cpp | 66 ------------------- .../SourceFiles/ui/effects/premium_top_bar.h | 4 -- 5 files changed, 71 insertions(+), 71 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 5ecab2999..86588f203 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -54,7 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/boost_box.h" #include "ui/controls/emoji_button.h" #include "ui/controls/userpic_button.h" -#include "ui/effects/premium_top_bar.h" +#include "ui/effects/premium_graphics.h" #include "ui/rect.h" #include "ui/rp_widget.h" #include "ui/vertical_list.h" diff --git a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp index d04dae62b..54361d545 100644 --- a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp @@ -27,7 +27,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_settings.h" #include "styles/style_window.h" +#include #include +#include namespace Ui { namespace Premium { @@ -903,6 +905,70 @@ void Line::recache(const QSize &s) { } // namespace +QString Svg() { + return u":/gui/icons/settings/star.svg"_q; +} + +QByteArray ColorizedSvg(const QGradientStops &gradientStops) { + auto f = QFile(Svg()); + if (!f.open(QIODevice::ReadOnly)) { + return QByteArray(); + } + auto content = QString::fromUtf8(f.readAll()); + auto stops = [&] { + auto s = QString(); + for (const auto &stop : gradientStops) { + s += QString("") + .arg(QString::number(stop.first), stop.second.name()); + } + return s; + }(); + const auto color = QString("%5") + .arg(0) + .arg(1) + .arg(1) + .arg(0) + .arg(std::move(stops)); + content.replace(u"gradientPlaceholder"_q, color); + content.replace(u"#fff"_q, u"url(#Gradient2)"_q); + f.close(); + return content.toUtf8(); +} + +QImage GenerateStarForLightTopBar(QRectF rect) { + auto svg = QSvgRenderer(Ui::Premium::Svg()); + + const auto size = rect.size().toSize(); + auto frame = QImage( + size * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + frame.setDevicePixelRatio(style::DevicePixelRatio()); + + auto mask = frame; + mask.fill(Qt::transparent); + { + auto p = QPainter(&mask); + auto gradient = QLinearGradient( + 0, + size.height(), + size.width(), + 0); + gradient.setStops(Ui::Premium::ButtonGradientStops()); + p.setPen(Qt::NoPen); + p.setBrush(gradient); + p.drawRect(0, 0, size.width(), size.height()); + } + frame.fill(Qt::transparent); + { + auto q = QPainter(&frame); + svg.render(&q, QRect(QPoint(), size)); + q.setCompositionMode(QPainter::CompositionMode_SourceIn); + q.drawImage(0, 0, mask); + } + return frame; +} + void AddBubbleRow( not_null parent, const style::PremiumBubble &st, diff --git a/Telegram/SourceFiles/ui/effects/premium_graphics.h b/Telegram/SourceFiles/ui/effects/premium_graphics.h index 339d49a3d..d1ba4f9d7 100644 --- a/Telegram/SourceFiles/ui/effects/premium_graphics.h +++ b/Telegram/SourceFiles/ui/effects/premium_graphics.h @@ -41,6 +41,10 @@ namespace Premium { inline constexpr auto kLimitRowRatio = 0.5; +[[nodiscard]] QString Svg(); +[[nodiscard]] QByteArray ColorizedSvg(const QGradientStops &gradientStops); +[[nodiscard]] QImage GenerateStarForLightTopBar(QRectF rect); + void AddBubbleRow( not_null parent, const style::PremiumBubble &st, diff --git a/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp b/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp index 640a96b3e..e2b619273 100644 --- a/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp @@ -16,8 +16,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_settings.h" #include "styles/style_premium.h" -#include - namespace Ui::Premium { namespace { @@ -42,70 +40,6 @@ constexpr auto kMinAcceptableContrast = 4.5; // 1.14; } // namespace -QString Svg() { - return u":/gui/icons/settings/star.svg"_q; -} - -QByteArray ColorizedSvg(const QGradientStops &gradientStops) { - auto f = QFile(Svg()); - if (!f.open(QIODevice::ReadOnly)) { - return QByteArray(); - } - auto content = QString::fromUtf8(f.readAll()); - auto stops = [&] { - auto s = QString(); - for (const auto &stop : gradientStops) { - s += QString("") - .arg(QString::number(stop.first), stop.second.name()); - } - return s; - }(); - const auto color = QString("%5") - .arg(0) - .arg(1) - .arg(1) - .arg(0) - .arg(std::move(stops)); - content.replace(u"gradientPlaceholder"_q, color); - content.replace(u"#fff"_q, u"url(#Gradient2)"_q); - f.close(); - return content.toUtf8(); -} - -QImage GenerateStarForLightTopBar(QRectF rect) { - auto svg = QSvgRenderer(Ui::Premium::Svg()); - - const auto size = rect.size().toSize(); - auto frame = QImage( - size * style::DevicePixelRatio(), - QImage::Format_ARGB32_Premultiplied); - frame.setDevicePixelRatio(style::DevicePixelRatio()); - - auto mask = frame; - mask.fill(Qt::transparent); - { - auto p = QPainter(&mask); - auto gradient = QLinearGradient( - 0, - size.height(), - size.width(), - 0); - gradient.setStops(Ui::Premium::ButtonGradientStops()); - p.setPen(Qt::NoPen); - p.setBrush(gradient); - p.drawRect(0, 0, size.width(), size.height()); - } - frame.fill(Qt::transparent); - { - auto q = QPainter(&frame); - svg.render(&q, QRect(QPoint(), size)); - q.setCompositionMode(QPainter::CompositionMode_SourceIn); - q.drawImage(0, 0, mask); - } - return frame; -} - TopBarAbstract::TopBarAbstract( QWidget *parent, const style::PremiumCover &st) diff --git a/Telegram/SourceFiles/ui/effects/premium_top_bar.h b/Telegram/SourceFiles/ui/effects/premium_top_bar.h index 86c8158ff..194527025 100644 --- a/Telegram/SourceFiles/ui/effects/premium_top_bar.h +++ b/Telegram/SourceFiles/ui/effects/premium_top_bar.h @@ -25,10 +25,6 @@ class FlatLabel; namespace Ui::Premium { -[[nodiscard]] QString Svg(); -[[nodiscard]] QByteArray ColorizedSvg(const QGradientStops &gradientStops); -[[nodiscard]] QImage GenerateStarForLightTopBar(QRectF rect); - class TopBarAbstract : public RpWidget { public: TopBarAbstract( From 1196b6a3fb8b48b6b93171edf4aead45a61c125e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 19 Jun 2024 17:09:25 +0300 Subject: [PATCH 061/142] Moved out generating of credits icon to td_ui. --- .../SourceFiles/settings/settings_credits.cpp | 7 ++- .../settings/settings_credits_graphics.cpp | 61 ++----------------- .../settings/settings_credits_graphics.h | 2 - .../ui/effects/credits_graphics.cpp | 57 +++++++++++++++++ .../SourceFiles/ui/effects/credits_graphics.h | 2 + 5 files changed, 67 insertions(+), 62 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 4cfeff843..08d7231c3 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_credits.h" -#include "settings/settings_credits_graphics.h" #include "api/api_credits.h" #include "boxes/gift_premium_box.h" #include "core/click_handler_types.h" @@ -20,8 +19,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "main/main_session.h" #include "settings/settings_common_session.h" +#include "settings/settings_credits_graphics.h" #include "statistics/widgets/chart_header_widget.h" #include "ui/boxes/boost_box.h" // Ui::StartFireworks. +#include "ui/effects/credits_graphics.h" #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_top_bar.h" #include "ui/layers/generic_box.h" @@ -93,8 +94,8 @@ Credits::Credits( not_null controller) : Section(parent) , _controller(controller) -, _star(GenerateStars(st::creditsTopupButton.height, 1)) -, _balanceStar(GenerateStars(st::creditsBalanceStarHeight, 1)) { +, _star(Ui::GenerateStars(st::creditsTopupButton.height, 1)) +, _balanceStar(Ui::GenerateStars(st::creditsBalanceStarHeight, 1)) { setupContent(); } diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 91a8c1625..a8aab17c3 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -109,59 +109,6 @@ private: } // namespace -QImage GenerateStars(int height, int count) { - constexpr auto kOutlineWidth = .6; - constexpr auto kStrokeWidth = 3; - constexpr auto kShift = 3; - - auto colorized = qs(Ui::Premium::ColorizedSvg( - Ui::Premium::CreditsIconGradientStops())); - colorized.replace( - u"stroke=\"none\""_q, - u"stroke=\"%1\""_q.arg(st::creditsStroke->c.name())); - colorized.replace( - u"stroke-width=\"1\""_q, - u"stroke-width=\"%1\""_q.arg(kStrokeWidth)); - auto svg = QSvgRenderer(colorized.toUtf8()); - svg.setViewBox(svg.viewBox() + Margins(kStrokeWidth)); - - const auto starSize = Size(height - kOutlineWidth * 2); - - auto frame = QImage( - QSize( - (height + kShift * (count - 1)) * style::DevicePixelRatio(), - height * style::DevicePixelRatio()), - QImage::Format_ARGB32_Premultiplied); - frame.setDevicePixelRatio(style::DevicePixelRatio()); - frame.fill(Qt::transparent); - const auto drawSingle = [&](QPainter &q) { - const auto s = kOutlineWidth; - q.save(); - q.translate(s, s); - q.setCompositionMode(QPainter::CompositionMode_Clear); - svg.render(&q, QRectF(QPointF(s, 0), starSize)); - svg.render(&q, QRectF(QPointF(s, s), starSize)); - svg.render(&q, QRectF(QPointF(0, s), starSize)); - svg.render(&q, QRectF(QPointF(-s, s), starSize)); - svg.render(&q, QRectF(QPointF(-s, 0), starSize)); - svg.render(&q, QRectF(QPointF(-s, -s), starSize)); - svg.render(&q, QRectF(QPointF(0, -s), starSize)); - svg.render(&q, QRectF(QPointF(s, -s), starSize)); - q.setCompositionMode(QPainter::CompositionMode_SourceOver); - svg.render(&q, Rect(starSize)); - q.restore(); - }; - { - auto q = QPainter(&frame); - q.translate(frame.width() / style::DevicePixelRatio() - height, 0); - for (auto i = count; i > 0; --i) { - drawSingle(q); - q.translate(-kShift, 0); - } - } - return frame; -} - void FillCreditOptions( not_null controller, not_null container, @@ -175,7 +122,7 @@ void FillCreditOptions( Ui::AddSkip(content, st::settingsPremiumOptionsPadding.top()); - const auto singleStarWidth = GenerateStars( + const auto singleStarWidth = Ui::GenerateStars( st::creditsTopupButton.height, 1).width() / style::DevicePixelRatio(); @@ -215,7 +162,7 @@ void FillCreditOptions( Ui::FillAmountAndCurrency(option.amount, option.currency), st::creditsTopupPrice); const auto inner = Ui::CreateChild(button); - const auto stars = GenerateStars(st.height, (i + 1)); + const auto stars = Ui::GenerateStars(st.height, (i + 1)); inner->paintRequest( ) | rpl::start_with_next([=](const QRect &rect) { auto p = QPainter(inner); @@ -314,7 +261,7 @@ not_null AddBalanceWidget( bool rightAlign) { const auto balance = Ui::CreateChild(parent); const auto balanceStar = balance->lifetime().make_state( - GenerateStars(st::creditsBalanceStarHeight, 1)); + Ui::GenerateStars(st::creditsBalanceStarHeight, 1)); const auto starSize = balanceStar->size() / style::DevicePixelRatio(); const auto label = balance->lifetime().make_state( st::defaultTextStyle, @@ -377,7 +324,7 @@ void ReceiptCreditsBox( box->setStyle(st::giveawayGiftCodeBox); box->setNoContentMargin(true); - const auto star = GenerateStars(st::creditsTopupButton.height, 1); + const auto star = Ui::GenerateStars(st::creditsTopupButton.height, 1); const auto content = box->verticalLayout(); Ui::AddSkip(content); diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index cb6c27e7e..d25b74688 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -28,8 +28,6 @@ class VerticalLayout; namespace Settings { -[[nodiscard]] QImage GenerateStars(int height, int count); - void FillCreditOptions( not_null controller, not_null container, diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index 3c7a714f7..ffc5a6503 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -16,13 +16,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "lang/lang_keys.h" #include "main/main_session.h" +#include "ui/effects/premium_graphics.h" #include "ui/empty_userpic.h" #include "ui/painter.h" +#include "ui/rect.h" #include "styles/style_credits.h" #include "styles/style_intro.h" // introFragmentIcon. #include "styles/style_settings.h" #include "styles/style_dialogs.h" +#include + namespace Ui { using PaintRoundImageCallback = Fn; +QImage GenerateStars(int height, int count) { + constexpr auto kOutlineWidth = .6; + constexpr auto kStrokeWidth = 3; + constexpr auto kShift = 3; + + auto colorized = qs(Ui::Premium::ColorizedSvg( + Ui::Premium::CreditsIconGradientStops())); + colorized.replace( + u"stroke=\"none\""_q, + u"stroke=\"%1\""_q.arg(st::creditsStroke->c.name())); + colorized.replace( + u"stroke-width=\"1\""_q, + u"stroke-width=\"%1\""_q.arg(kStrokeWidth)); + auto svg = QSvgRenderer(colorized.toUtf8()); + svg.setViewBox(svg.viewBox() + Margins(kStrokeWidth)); + + const auto starSize = Size(height - kOutlineWidth * 2); + + auto frame = QImage( + QSize( + (height + kShift * (count - 1)) * style::DevicePixelRatio(), + height * style::DevicePixelRatio()), + QImage::Format_ARGB32_Premultiplied); + frame.setDevicePixelRatio(style::DevicePixelRatio()); + frame.fill(Qt::transparent); + const auto drawSingle = [&](QPainter &q) { + const auto s = kOutlineWidth; + q.save(); + q.translate(s, s); + q.setCompositionMode(QPainter::CompositionMode_Clear); + svg.render(&q, QRectF(QPointF(s, 0), starSize)); + svg.render(&q, QRectF(QPointF(s, s), starSize)); + svg.render(&q, QRectF(QPointF(0, s), starSize)); + svg.render(&q, QRectF(QPointF(-s, s), starSize)); + svg.render(&q, QRectF(QPointF(-s, 0), starSize)); + svg.render(&q, QRectF(QPointF(-s, -s), starSize)); + svg.render(&q, QRectF(QPointF(0, -s), starSize)); + svg.render(&q, QRectF(QPointF(s, -s), starSize)); + q.setCompositionMode(QPainter::CompositionMode_SourceOver); + svg.render(&q, Rect(starSize)); + q.restore(); + }; + { + auto q = QPainter(&frame); + q.translate(frame.width() / style::DevicePixelRatio() - height, 0); + for (auto i = count; i > 0; --i) { + drawSingle(q); + q.translate(-kShift, 0); + } + } + return frame; +} + PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( const Data::CreditsHistoryEntry &entry) { const auto bg = [&]() -> Ui::EmptyUserpic::BgColors { diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.h b/Telegram/SourceFiles/ui/effects/credits_graphics.h index ddfbea07c..1d0751605 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.h +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.h @@ -15,6 +15,8 @@ struct CreditsHistoryEntry; namespace Ui { +[[nodiscard]] QImage GenerateStars(int height, int count); + Fn GenerateCreditsPaintUserpicCallback( const Data::CreditsHistoryEntry &entry); From db4c9b83f3043f47a1b1ae4c6a381a9046da4948 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 19 Jun 2024 18:09:32 +0300 Subject: [PATCH 062/142] Added credits icon to statistical charts. --- .../statistics/statistics_graphics.cpp | 43 +++++++++++++++++++ .../statistics/statistics_graphics.h | 20 +++++++++ .../statistics/view/chart_rulers_view.cpp | 13 +++--- .../statistics/view/chart_rulers_view.h | 2 +- .../widgets/point_details_widget.cpp | 23 +++++----- .../statistics/widgets/point_details_widget.h | 2 +- Telegram/cmake/td_ui.cmake | 2 + 7 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 Telegram/SourceFiles/statistics/statistics_graphics.cpp create mode 100644 Telegram/SourceFiles/statistics/statistics_graphics.h diff --git a/Telegram/SourceFiles/statistics/statistics_graphics.cpp b/Telegram/SourceFiles/statistics/statistics_graphics.cpp new file mode 100644 index 000000000..167082a5a --- /dev/null +++ b/Telegram/SourceFiles/statistics/statistics_graphics.cpp @@ -0,0 +1,43 @@ +/* +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 "statistics/statistics_graphics.h" + +#include "data/data_statistics_chart.h" +#include "ui/effects/credits_graphics.h" // GenerateStars. +#include "ui/painter.h" +#include "styles/style_basic.h" +#include "styles/style_statistics.h" + +namespace Statistic { + +QImage ChartCurrencyIcon( + const Data::StatisticalChart &chartData, + std::optional color) { + auto result = QImage(); + const auto iconSize = st::statisticsCurrencyIcon.size(); + if (chartData.currency == Data::StatisticalCurrency::Ton) { + result = QImage( + iconSize * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(style::DevicePixelRatio()); + result.fill(Qt::transparent); + { + auto p = Painter(&result); + if (const auto w = iconSize.width(); w && color) { + st::statisticsCurrencyIcon.paint(p, 0, 0, w, *color); + } else { + st::statisticsCurrencyIcon.paint(p, 0, 0, iconSize.width()); + } + } + } else if (chartData.currency == Data::StatisticalCurrency::Credits) { + return Ui::GenerateStars(iconSize.height(), 1); + } + return result; +} + +} // namespace Statistic diff --git a/Telegram/SourceFiles/statistics/statistics_graphics.h b/Telegram/SourceFiles/statistics/statistics_graphics.h new file mode 100644 index 000000000..e6c3a8011 --- /dev/null +++ b/Telegram/SourceFiles/statistics/statistics_graphics.h @@ -0,0 +1,20 @@ +/* +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 + +namespace Data { +struct StatisticalChart; +} // namespace Data + +namespace Statistic { + +[[nodiscard]] QImage ChartCurrencyIcon( + const Data::StatisticalChart &chartData, + std::optional color); + +} // namespace Statistic diff --git a/Telegram/SourceFiles/statistics/view/chart_rulers_view.cpp b/Telegram/SourceFiles/statistics/view/chart_rulers_view.cpp index 6925f30e5..ac9dc4582 100644 --- a/Telegram/SourceFiles/statistics/view/chart_rulers_view.cpp +++ b/Telegram/SourceFiles/statistics/view/chart_rulers_view.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "statistics/chart_lines_filter_controller.h" #include "statistics/statistics_common.h" +#include "statistics/statistics_graphics.h" #include "styles/style_basic.h" #include "styles/style_statistics.h" @@ -37,7 +38,7 @@ void ChartRulersView::setChartData( _isDouble = (type == ChartViewType::DoubleLinear) || chartData.currencyRate; if (chartData.currencyRate) { - _currencyIcon = &st::statisticsCurrencyIcon; + _currencyIcon = ChartCurrencyIcon(chartData, {}); _leftCustomCaption = [=](float64 value) { return FormatF(value / float64(Data::kEarnMultiplier)); }; @@ -92,7 +93,9 @@ void ChartRulersView::paintCaptionsToRulers( for (auto &ruler : _rulers) { const auto rulerAlpha = alpha * ruler.alpha; p.setOpacity(rulerAlpha); - const auto left = _currencyIcon ? _currencyIcon->width() : 0; + const auto left = _currencyIcon.isNull() + ? 0 + : _currencyIcon.width() / style::DevicePixelRatio(); for (const auto &line : ruler.lines) { const auto y = offset + r.height() * line.relativeValue; const auto hasLinesFilter = _isDouble && _linesFilter; @@ -102,11 +105,11 @@ void ChartRulersView::paintCaptionsToRulers( } else { p.setPen(st::windowSubTextFg); } - if (_currencyIcon) { + if (!_currencyIcon.isNull()) { const auto iconTop = y - - _currencyIcon->height() + - _currencyIcon.height() / style::DevicePixelRatio() + st::statisticsChartRulerCaptionSkip; - _currencyIcon->paint(p, 0, iconTop, r.width()); + p.drawImage(0, iconTop, _currencyIcon); } p.drawText( left, diff --git a/Telegram/SourceFiles/statistics/view/chart_rulers_view.h b/Telegram/SourceFiles/statistics/view/chart_rulers_view.h index 44326d15e..8ce8e989f 100644 --- a/Telegram/SourceFiles/statistics/view/chart_rulers_view.h +++ b/Telegram/SourceFiles/statistics/view/chart_rulers_view.h @@ -42,7 +42,7 @@ private: QPen _rightPen; int _leftLineId = 0; int _rightLineId = 0; - const style::icon *_currencyIcon = nullptr; + QImage _currencyIcon; Fn _leftCustomCaption = nullptr; Fn _rightCustomCaption = nullptr; diff --git a/Telegram/SourceFiles/statistics/widgets/point_details_widget.cpp b/Telegram/SourceFiles/statistics/widgets/point_details_widget.cpp index 305010866..f96c4ac3a 100644 --- a/Telegram/SourceFiles/statistics/widgets/point_details_widget.cpp +++ b/Telegram/SourceFiles/statistics/widgets/point_details_widget.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "statistics/statistics_common.h" #include "statistics/statistics_format_values.h" +#include "statistics/statistics_graphics.h" #include "statistics/view/stack_linear_chart_common.h" #include "ui/cached_round_corners.h" #include "ui/effects/ripple_animation.h" @@ -135,9 +136,7 @@ PointDetailsWidget::PointDetailsWidget( , _zoomEnabled(zoomEnabled) , _chartData(chartData) , _textStyle(st::statisticsDetailsPopupStyle) -, _headerStyle(st::statisticsDetailsPopupHeaderStyle) -, _valueIcon(chartData.currencyRate ? &st::statisticsCurrencyIcon : nullptr) { - +, _headerStyle(st::statisticsDetailsPopupHeaderStyle) { if (zoomEnabled) { rpl::single(rpl::empty_value()) | rpl::then( style::PaletteChanged() @@ -205,7 +204,9 @@ PointDetailsWidget::PointDetailsWidget( + rect::m::sum::h(st::statisticsDetailsPopupPadding) + st::statisticsDetailsPopupPadding.left() // Between strings. + maxNameTextWidth - + (_valueIcon ? _valueIcon->width() : 0) + + (_valueIcon.isNull() + ? 0 + : _valueIcon.width() / style::DevicePixelRatio()) + _maxPercentageWidth; }(); sizeValue( @@ -310,6 +311,9 @@ void PointDetailsWidget::setXIndex(int xIndex) { } _lines.push_back(std::move(textLine)); } + if (_chartData.currencyRate && _valueIcon.isNull()) { + _valueIcon = ChartCurrencyIcon(_chartData, _lines.front().valueColor); + } const auto clickable = _zoomEnabled && hasPositiveValues; _hasPositiveValues = hasPositiveValues; QWidget::setAttribute( @@ -408,13 +412,12 @@ void PointDetailsWidget::paintEvent(QPaintEvent *e) { .outerWidth = _textRect.width(), .availableWidth = valueWidth, }; - if (!i && _valueIcon) { - _valueIcon->paint( - p, - valueContext.position.x() - _valueIcon->width(), + if (!i && !_valueIcon.isNull()) { + p.drawImage( + valueContext.position.x() + - _valueIcon.width() / style::DevicePixelRatio(), lineY, - valueContext.outerWidth, - line.valueColor); + _valueIcon); } const auto nameContext = Ui::Text::PaintContext{ .position = QPoint( diff --git a/Telegram/SourceFiles/statistics/widgets/point_details_widget.h b/Telegram/SourceFiles/statistics/widgets/point_details_widget.h index a1e3dcccc..cdf6b3bd3 100644 --- a/Telegram/SourceFiles/statistics/widgets/point_details_widget.h +++ b/Telegram/SourceFiles/statistics/widgets/point_details_widget.h @@ -47,7 +47,7 @@ private: const style::TextStyle &_textStyle; const style::TextStyle &_headerStyle; Ui::Text::String _header; - const style::icon *_valueIcon = nullptr; + QImage _valueIcon; void invalidateCache(); diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index ca5cb6003..34063bb22 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -214,6 +214,8 @@ PRIVATE statistics/statistics_data_deserialize.h statistics/statistics_format_values.cpp statistics/statistics_format_values.h + statistics/statistics_graphics.cpp + statistics/statistics_graphics.h statistics/statistics_types.h statistics/view/abstract_chart_view.cpp statistics/view/abstract_chart_view.h From eb997ae9e3381f9f585c991ef8dc7ae81e348128 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 20 Jun 2024 06:00:02 +0300 Subject: [PATCH 063/142] Added initial implementation of filter for MTP updates. --- Telegram/CMakeLists.txt | 1 + Telegram/SourceFiles/api/api_filter_updates.h | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 Telegram/SourceFiles/api/api_filter_updates.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 686bb93be..fc0f622fc 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -126,6 +126,7 @@ PRIVATE api/api_earn.h api/api_editing.cpp api/api_editing.h + api/api_filter_updates.h api/api_global_privacy.cpp api/api_global_privacy.h api/api_hash.cpp diff --git a/Telegram/SourceFiles/api/api_filter_updates.h b/Telegram/SourceFiles/api/api_filter_updates.h new file mode 100644 index 000000000..0126d3736 --- /dev/null +++ b/Telegram/SourceFiles/api/api_filter_updates.h @@ -0,0 +1,27 @@ +/* +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 + +namespace Api { + +template +void PerformForUpdate( + const MTPUpdates &updates, + Fn callback) { + updates.match([&](const MTPDupdates &updates) { + for (const auto &update : updates.vupdates().v) { + update.match([&](const Type &d) { + callback(d); + }, [](const auto &) { + }); + } + }, [](const auto &) { + }); +} + +} // namespace Api From 9eebd3b514c4fac3507b5692849430fda0b658f3 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 20 Jun 2024 06:05:19 +0300 Subject: [PATCH 064/142] Added statistical chart and balance overview to bot earn section. --- Telegram/Resources/langs/lang.strings | 13 + .../info/bot/earn/info_earn_inner_widget.cpp | 337 ++++++++++++++++++ .../info/bot/earn/info_earn_inner_widget.h | 1 + .../earn/channel_earn.style | 6 + .../earn/info_earn_inner_widget.cpp | 3 +- 5 files changed, 358 insertions(+), 2 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 90cffa321..924481f4b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -5188,6 +5188,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_channel_earn_chart_overriden_detail_usd" = "Revenue in USD"; "lng_bot_earn_title" = "Stars Balance"; +"lng_bot_earn_chart_revenue" = "Revenue"; +"lng_bot_earn_overview_title" = "Proceeds overview"; +"lng_bot_earn_available" = "Available balance"; +"lng_bot_earn_total" = "Total lifetime proceeds"; +"lng_bot_earn_balance_title" = "Available balance"; +"lng_bot_earn_balance_about" = "Stars from your total balance become available for spending on ads and rewards 21 days after they are earned."; +"lng_bot_earn_balance_about_url" = "https://telegram.org/tos/stars"; +"lng_bot_earn_balance_button#one" = "Withdraw {emoji} {count} via Fragment"; +"lng_bot_earn_balance_button#other" = "Withdraw {emoji} {count} via Fragment"; +"lng_bot_earn_balance_button_all" = "Withdraw all stars via Fragment"; +"lng_bot_earn_balance_button_locked" = "Withdraw via Fragment"; +"lng_bot_earn_learn_credits_out_about" = "You can withdraw Stars using Fragment, or use Stars to advertise your bot. {link}"; +"lng_bot_earn_out_ph" = "Enter amount to withdraw"; "lng_contact_add" = "Add"; "lng_contact_send_message" = "message"; diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp index 04c134a07..230219c36 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp @@ -7,12 +7,64 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/bot/earn/info_earn_inner_widget.h" +#include "api/api_credits.h" +#include "api/api_filter_updates.h" +#include "data/data_channel_earn.h" +#include "data/data_session.h" #include "data/data_user.h" +#include "data/stickers/data_custom_emoji.h" #include "info/bot/earn/info_earn_widget.h" +#include "info/channel_statistics/earn/earn_format.h" #include "info/info_controller.h" +#include "info/statistics/info_statistics_inner_widget.h" // FillLoading. +#include "lang/lang_keys.h" +#include "main/main_account.h" +#include "main/main_session.h" +#include "statistics/chart_widget.h" +#include "ui/effects/credits_graphics.h" +#include "ui/rect.h" +#include "ui/text/text_utilities.h" +#include "ui/vertical_list.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/fields/number_input.h" +#include "ui/widgets/label_with_custom_emoji.h" +#include "ui/widgets/labels.h" +#include "styles/style_boxes.h" +#include "styles/style_channel_earn.h" +#include "styles/style_chat.h" +#include "styles/style_layers.h" +#include "styles/style_statistics.h" namespace Info::BotEarn { namespace { + +void AddHeader( + not_null content, + tr::phrase<> text) { + Ui::AddSkip(content); + const auto header = content->add( + object_ptr( + content, + text(), + st::channelEarnHeaderLabel), + st::boxRowPadding); + header->resizeToWidth(header->width()); +} + +[[nodiscard]] not_null CreateIconWidget( + not_null parent, + QImage image) { + const auto widget = Ui::CreateChild(parent); + widget->resize(image.size() / style::DevicePixelRatio()); + widget->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(widget); + p.drawImage(0, 0, image); + }, widget->lifetime()); + widget->setAttribute(Qt::WA_TransparentForMouseEvents); + return widget; +} + } // namespace InnerWidget::InnerWidget( @@ -26,9 +78,294 @@ InnerWidget::InnerWidget( } void InnerWidget::load() { + const auto apiLifetime = lifetime().make_state(); + + const auto request = [=](Fn done) { + const auto api = apiLifetime->make_state( + _peer->asUser()); + api->request( + ) | rpl::start_with_error_done([show = _show](const QString &error) { + show->showToast(error); + }, [=] { + done(api->data()); + apiLifetime->destroy(); + }, *apiLifetime); + }; + + Info::Statistics::FillLoading( + this, + _loaded.events_starting_with(false) | rpl::map(!rpl::mappers::_1), + _showFinished.events()); + + _showFinished.events( + ) | rpl::take(1) | rpl::start_with_next([=] { + request([=](Data::BotEarnStatistics state) { + _state = state; + _loaded.fire(true); + fill(); + + _peer->session().account().mtpUpdates( + ) | rpl::start_with_next([=](const MTPUpdates &updates) { + using TL = MTPDupdateStarsRevenueStatus; + Api::PerformForUpdate(updates, [&](const TL &d) { + const auto peerId = peerFromMTP(d.vpeer()); + if (peerId == _peer->id) { + request([=](Data::BotEarnStatistics state) { + _state = state; + _stateUpdated.fire({}); + }); + } + }); + }, lifetime()); + }); + }, lifetime()); } void InnerWidget::fill() { + using namespace Info::ChannelEarn; + const auto container = this; + const auto &data = _state; + const auto multiplier = data.usdRate * Data::kEarnMultiplier; + const auto session = &_peer->session(); + + auto availableBalanceValue = rpl::single( + data.availableBalance + ) | rpl::then( + _stateUpdated.events() | rpl::map([=] { + return _state.availableBalance; + }) + ); + auto valueToString = [](uint64 v) { return QString::number(v); }; + + if (data.revenueGraph.chart) { + Ui::AddSkip(container); + Ui::AddSkip(container); + using Type = Statistic::ChartViewType; + const auto widget = container->add( + object_ptr(container), + st::statisticsLayerMargins); + + auto chart = data.revenueGraph.chart; + chart.currencyRate = data.usdRate; + + widget->setChartData(chart, Type::StackBar); + widget->setTitle(tr::lng_bot_earn_chart_revenue()); + Ui::AddSkip(container); + Ui::AddDivider(container); + Ui::AddSkip(container); + Ui::AddSkip(container); + } + { + AddHeader(container, tr::lng_bot_earn_overview_title); + Ui::AddSkip(container, st::channelEarnOverviewTitleSkip); + + const auto addOverview = [&]( + rpl::producer value, + const tr::phrase<> &text) { + const auto line = container->add( + Ui::CreateSkipWidget(container, 0), + st::boxRowPadding); + const auto majorLabel = Ui::CreateChild( + line, + rpl::duplicate(value) | rpl::map(valueToString), + st::channelEarnOverviewMajorLabel); + const auto icon = CreateIconWidget( + line, + Ui::GenerateStars(majorLabel->height(), 1)); + const auto secondMinorLabel = Ui::CreateChild( + line, + std::move( + value + ) | rpl::map([=](uint64 v) { + return v ? ToUsd(v, multiplier) : QString(); + }), + st::channelEarnOverviewSubMinorLabel); + rpl::combine( + line->widthValue(), + majorLabel->sizeValue() + ) | rpl::start_with_next([=](int available, const QSize &size) { + line->resize(line->width(), size.height()); + majorLabel->moveToLeft( + icon->width() + st::channelEarnOverviewMinorLabelSkip, + majorLabel->y()); + secondMinorLabel->resizeToWidth(available + - size.width() + - icon->width()); + secondMinorLabel->moveToLeft( + rect::right(majorLabel) + + st::channelEarnOverviewSubMinorLabelPos.x(), + st::channelEarnOverviewSubMinorLabelPos.y()); + }, majorLabel->lifetime()); + Ui::ToggleChildrenVisibility(line, true); + + Ui::AddSkip(container); + const auto sub = container->add( + object_ptr( + container, + text(), + st::channelEarnOverviewSubMinorLabel), + st::boxRowPadding); + }; + addOverview( + rpl::duplicate(availableBalanceValue), + tr::lng_bot_earn_available); + Ui::AddSkip(container); + Ui::AddSkip(container); + // addOverview(data.currentBalance, tr::lng_bot_earn_reward); + // Ui::AddSkip(container); + // Ui::AddSkip(container); + addOverview( + rpl::single( + data.overallRevenue + ) | rpl::then( + _stateUpdated.events() | rpl::map([=] { + return _state.overallRevenue; + }) + ), + tr::lng_bot_earn_total); + Ui::AddSkip(container); + Ui::AddSkip(container); + Ui::AddDividerText(container, tr::lng_bot_earn_balance_about()); + Ui::AddSkip(container); + } + { + AddHeader(container, tr::lng_bot_earn_balance_title); + Ui::AddSkip(container); + + const auto labels = container->add( + object_ptr>( + container, + object_ptr(container)))->entity(); + + const auto majorLabel = Ui::CreateChild( + labels, + rpl::duplicate(availableBalanceValue) | rpl::map(valueToString), + st::channelEarnBalanceMajorLabel); + const auto icon = CreateIconWidget( + labels, + Ui::GenerateStars(majorLabel->height(), 1)); + majorLabel->setAttribute(Qt::WA_TransparentForMouseEvents); + majorLabel->sizeValue( + ) | rpl::start_with_next([=](const QSize &majorSize) { + const auto skip = st::channelEarnBalanceMinorLabelSkip; + labels->resize( + majorSize.width() + icon->width() + skip, + majorSize.height()); + majorLabel->moveToLeft(icon->width() + skip, 0); + }, labels->lifetime()); + Ui::ToggleChildrenVisibility(labels, true); + + Ui::AddSkip(container); + container->add( + object_ptr>( + container, + object_ptr( + container, + rpl::duplicate( + availableBalanceValue + ) | rpl::map([=](uint64 v) { + return v ? ToUsd(v, multiplier) : QString(); + }), + st::channelEarnOverviewSubMinorLabel))); + + Ui::AddSkip(container); + + { + const auto &st = st::botEarnInputField; + const auto inputContainer = container->add( + Ui::CreateSkipWidget(container, st.heightMin)); + const auto currentValue = rpl::variable( + rpl::duplicate(availableBalanceValue)); + const auto input = Ui::CreateChild( + inputContainer, + st, + tr::lng_bot_earn_out_ph(), + QString::number(currentValue.current()), + currentValue.current()); + rpl::duplicate( + availableBalanceValue + ) | rpl::start_with_next([=](uint64 v) { + input->changeLimit(v); + input->setText(QString::number(v)); + }, input->lifetime()); + const auto icon = CreateIconWidget( + inputContainer, + Ui::GenerateStars(st.style.font->height, 1)); + inputContainer->sizeValue( + ) | rpl::start_with_next([=](const QSize &size) { + input->resize( + size.width() - rect::m::sum::h(st::boxRowPadding), + st.heightMin); + input->moveToLeft(st::boxRowPadding.left(), 0); + icon->moveToLeft( + st::boxRowPadding.left(), + st.textMargins.top()); + }, input->lifetime()); + Ui::ToggleChildrenVisibility(inputContainer, true); + } + + Ui::AddSkip(container); + Ui::AddSkip(container); + + const auto &stButton = st::defaultActiveButton; + const auto button = container->add( + object_ptr( + container, + rpl::never(), + stButton), + st::boxRowPadding); + + const auto label = Ui::CreateChild( + button, + tr::lng_channel_earn_balance_button(tr::now), + st::channelEarnSemiboldLabel); + label->setTextColorOverride(stButton.textFg->c); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + rpl::combine( + button->sizeValue(), + label->sizeValue() + ) | rpl::start_with_next([=](const QSize &b, const QSize &l) { + label->moveToLeft( + (b.width() - l.width()) / 2, + (b.height() - l.height()) / 2); + }, label->lifetime()); + + // Api::HandleWithdrawalButton(_peer, button, _controller->uiShow()); + Ui::ToggleChildrenVisibility(button, true); + + Ui::AddSkip(container); + Ui::AddSkip(container); + + const auto arrow = Ui::Text::SingleCustomEmoji( + session->data().customEmojiManager().registerInternalEmoji( + st::topicButtonArrow, + st::channelEarnLearnArrowMargins, + false)); + auto about = Ui::CreateLabelWithCustomEmoji( + container, + tr::lng_bot_earn_learn_credits_out_about( + lt_link, + tr::lng_channel_earn_about_link( + lt_emoji, + rpl::single(arrow), + Ui::Text::RichLangValue + ) | rpl::map([](TextWithEntities text) { + return Ui::Text::Link( + std::move(text), + tr::lng_bot_earn_balance_about_url(tr::now)); + }), + Ui::Text::RichLangValue), + { .session = session }, + st::boxDividerLabel); + Ui::AddSkip(container); + container->add(object_ptr( + container, + std::move(about), + st::defaultBoxDividerLabelPadding, + RectPart::Top | RectPart::Bottom)); + + Ui::AddSkip(container); + } } void InnerWidget::saveState(not_null memento) { diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h index f1d6d470c..b3bc5655a 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h @@ -63,6 +63,7 @@ private: rpl::event_stream<> _showFinished; rpl::event_stream<> _focusRequested; rpl::event_stream _loaded; + rpl::event_stream<> _stateUpdated; }; diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/channel_earn.style b/Telegram/SourceFiles/info/channel_statistics/earn/channel_earn.style index 6c2304141..11cd41680 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/channel_earn.style +++ b/Telegram/SourceFiles/info/channel_statistics/earn/channel_earn.style @@ -136,3 +136,9 @@ sponsoredReportLabel: FlatLabel(defaultFlatLabel) { style: boxTextStyle; minWidth: 150px; } + +botEarnInputField: InputField(defaultInputField) { + textMargins: margins(23px, 28px, 0px, 4px); + width: 100px; + heightMax: 55px; +} diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp index 6e13c31fc..f33f93ea3 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp @@ -345,8 +345,7 @@ void InnerWidget::fill() { ) | rpl::map([](TextWithEntities text) { return Ui::Text::Link(std::move(text), 1); }), - Ui::Text::RichLangValue - ), + Ui::Text::RichLangValue), { .session = session }, st::boxDividerLabel); label->setLink(1, std::make_shared([=] { From 989145726d7beac576554df8dff94f192fb0ba2f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 20 Jun 2024 06:24:07 +0300 Subject: [PATCH 065/142] Made withdrawal button handler in earn sections more universal. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/api/api_earn.cpp | 52 +++++++++++++------ Telegram/SourceFiles/api/api_earn.h | 8 ++- .../earn/info_earn_inner_widget.cpp | 5 +- 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 924481f4b..02a263f42 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -5201,6 +5201,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_earn_balance_button_locked" = "Withdraw via Fragment"; "lng_bot_earn_learn_credits_out_about" = "You can withdraw Stars using Fragment, or use Stars to advertise your bot. {link}"; "lng_bot_earn_out_ph" = "Enter amount to withdraw"; +"lng_bot_earn_balance_password_title" = "Two-step verification"; +"lng_bot_earn_balance_password_description" = "Please enter your password to collect."; "lng_contact_add" = "Add"; "lng_contact_send_message" = "message"; diff --git a/Telegram/SourceFiles/api/api_earn.cpp b/Telegram/SourceFiles/api/api_earn.cpp index d6425ef69..724fbc28c 100644 --- a/Telegram/SourceFiles/api/api_earn.cpp +++ b/Telegram/SourceFiles/api/api_earn.cpp @@ -34,16 +34,21 @@ void RestrictSponsored( } void HandleWithdrawalButton( - not_null channel, + RewardReceiver receiver, not_null button, std::shared_ptr show) { + Expects(receiver.currencyReceiver + || (receiver.creditsReceiver && receiver.creditsAmount)); struct State { rpl::lifetime lifetime; bool loading = false; }; + const auto channel = receiver.currencyReceiver; + const auto peer = receiver.creditsReceiver; + const auto state = button->lifetime().make_state(); - const auto session = &channel->session(); + const auto session = (channel ? &channel->session() : &peer->session()); session->api().cloudPassword().reload(); button->setClickedCallback([=] { @@ -58,10 +63,12 @@ void HandleWithdrawalButton( state->loading = false; auto fields = PasscodeBox::CloudFields::From(pass); - fields.customTitle - = tr::lng_channel_earn_balance_password_title(); - fields.customDescription - = tr::lng_channel_earn_balance_password_description(tr::now); + fields.customTitle = channel + ? tr::lng_channel_earn_balance_password_title() + : tr::lng_bot_earn_balance_password_title(); + fields.customDescription = channel + ? tr::lng_channel_earn_balance_password_description(tr::now) + : tr::lng_bot_earn_balance_password_description(tr::now); fields.customSubmitButton = tr::lng_passcode_submit(); fields.customCheckCallback = crl::guard(button, [=]( const Core::CloudPasswordResult &result, @@ -77,15 +84,30 @@ void HandleWithdrawalButton( const auto fail = [=](const QString &error) { show->showToast(error); }; - session->api().request( - MTPstats_GetBroadcastRevenueWithdrawalUrl( - channel->inputChannel, - result.result - )).done([=](const MTPstats_BroadcastRevenueWithdrawalUrl &r) { - done(qs(r.data().vurl())); - }).fail([=](const MTP::Error &error) { - fail(error.type()); - }).send(); + if (channel) { + session->api().request( + MTPstats_GetBroadcastRevenueWithdrawalUrl( + channel->inputChannel, + result.result + )).done([=]( + const MTPstats_BroadcastRevenueWithdrawalUrl &r) { + done(qs(r.data().vurl())); + }).fail([=](const MTP::Error &error) { + fail(error.type()); + }).send(); + } else if (peer) { + session->api().request( + MTPpayments_GetStarsRevenueWithdrawalUrl( + peer->input, + MTP_long(receiver.creditsAmount()), + result.result + )).done([=]( + const MTPpayments_StarsRevenueWithdrawalUrl &r) { + done(qs(r.data().vurl())); + }).fail([=](const MTP::Error &error) { + fail(error.type()); + }).send(); + } }); show->show(Box(session, fields)); }); diff --git a/Telegram/SourceFiles/api/api_earn.h b/Telegram/SourceFiles/api/api_earn.h index 93f2bf6eb..cbee5d25a 100644 --- a/Telegram/SourceFiles/api/api_earn.h +++ b/Telegram/SourceFiles/api/api_earn.h @@ -21,8 +21,14 @@ void RestrictSponsored( bool restricted, Fn failed); +struct RewardReceiver final { + ChannelData *currencyReceiver = nullptr; + PeerData *creditsReceiver = nullptr; + Fn creditsAmount; +}; + void HandleWithdrawalButton( - not_null channel, + RewardReceiver receiver, not_null button, std::shared_ptr show); diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp index f33f93ea3..4516135ae 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp @@ -714,7 +714,10 @@ void InnerWidget::fill() { !withdrawalEnabled); #endif - Api::HandleWithdrawalButton(channel, button, _controller->uiShow()); + Api::HandleWithdrawalButton( + { .currencyReceiver = channel }, + button, + _controller->uiShow()); Ui::ToggleChildrenVisibility(button, true); Ui::AddSkip(container); From 3c4a711b5d33c2de19f01dd6d2cfc1d0762b13f8 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 20 Jun 2024 06:40:28 +0300 Subject: [PATCH 066/142] Added withdrawal button to bot earn sections. --- .../info/bot/earn/info_earn_inner_widget.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp index 230219c36..93df61130 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/bot/earn/info_earn_inner_widget.h" +#include "api/api_earn.h" #include "api/api_credits.h" #include "api/api_filter_updates.h" #include "data/data_channel_earn.h" @@ -270,7 +271,7 @@ void InnerWidget::fill() { Ui::AddSkip(container); - { + const auto input = [&] { const auto &st = st::botEarnInputField; const auto inputContainer = container->add( Ui::CreateSkipWidget(container, st.heightMin)); @@ -302,7 +303,8 @@ void InnerWidget::fill() { st.textMargins.top()); }, input->lifetime()); Ui::ToggleChildrenVisibility(inputContainer, true); - } + return input; + }(); Ui::AddSkip(container); Ui::AddSkip(container); @@ -330,7 +332,13 @@ void InnerWidget::fill() { (b.height() - l.height()) / 2); }, label->lifetime()); - // Api::HandleWithdrawalButton(_peer, button, _controller->uiShow()); + Api::HandleWithdrawalButton( + Api::RewardReceiver{ + .creditsReceiver = _peer, + .creditsAmount = [=] { return input->getLastText().toInt(); }, + }, + button, + _controller->uiShow()); Ui::ToggleChildrenVisibility(button, true); Ui::AddSkip(container); From 301ffc15ef14ea5fb85768ecd2e8ccd54360c653 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 20 Jun 2024 19:34:59 +0300 Subject: [PATCH 067/142] Added countdown label to withdrawal button in bot earn section. --- .../info/bot/earn/info_earn_inner_widget.cpp | 110 +++++++++++++++++- .../earn/channel_earn.style | 7 ++ 2 files changed, 114 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp index 93df61130..2ba9e6b04 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp @@ -7,9 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/bot/earn/info_earn_inner_widget.h" -#include "api/api_earn.h" #include "api/api_credits.h" +#include "api/api_earn.h" #include "api/api_filter_updates.h" +#include "base/timer_rpl.h" +#include "base/unixtime.h" +#include "core/ui_integration.h" #include "data/data_channel_earn.h" #include "data/data_session.h" #include "data/data_user.h" @@ -309,6 +312,19 @@ void InnerWidget::fill() { Ui::AddSkip(container); Ui::AddSkip(container); + auto dateValue = rpl::single( + data.nextWithdrawalAt + ) | rpl::then( + _stateUpdated.events() | rpl::map([=] { + return _state.nextWithdrawalAt; + }) + ); + auto lockedValue = rpl::duplicate( + dateValue + ) | rpl::map([=](const QDateTime &dt) { + return !dt.isNull() || (!_state.isWithdrawalEnabled); + }); + const auto &stButton = st::defaultActiveButton; const auto button = container->add( object_ptr( @@ -317,6 +333,12 @@ void InnerWidget::fill() { stButton), st::boxRowPadding); + rpl::duplicate( + lockedValue + ) | rpl::start_with_next([=](bool v) { + button->setAttribute(Qt::WA_TransparentForMouseEvents, v); + }, button->lifetime()); + const auto label = Ui::CreateChild( button, tr::lng_channel_earn_balance_button(tr::now), @@ -324,14 +346,96 @@ void InnerWidget::fill() { label->setTextColorOverride(stButton.textFg->c); label->setAttribute(Qt::WA_TransparentForMouseEvents); rpl::combine( + rpl::duplicate(lockedValue), button->sizeValue(), label->sizeValue() - ) | rpl::start_with_next([=](const QSize &b, const QSize &l) { + ) | rpl::start_with_next([=](bool v, const QSize &b, const QSize &l) { label->moveToLeft( (b.width() - l.width()) / 2, - (b.height() - l.height()) / 2); + (v ? -10 : 1) * (b.height() - l.height()) / 2); }, label->lifetime()); + const auto lockedColor = anim::with_alpha(stButton.textFg->c, .5); + const auto lockedLabelTop = Ui::CreateChild( + button, + tr::lng_bot_earn_balance_button_locked(), + st::botEarnLockedButtonLabel); + lockedLabelTop->setTextColorOverride(lockedColor); + lockedLabelTop->setAttribute(Qt::WA_TransparentForMouseEvents); + const auto lockedLabelBottom = Ui::CreateChild( + button, + QString(), + st::botEarnLockedButtonLabel); + lockedLabelBottom->setTextColorOverride(lockedColor); + lockedLabelBottom->setAttribute(Qt::WA_TransparentForMouseEvents); + rpl::combine( + rpl::duplicate(lockedValue), + button->sizeValue(), + lockedLabelTop->sizeValue(), + lockedLabelBottom->sizeValue() + ) | rpl::start_with_next([=]( + bool locked, + const QSize &b, + const QSize &top, + const QSize &bottom) { + const auto factor = locked ? 1 : -10; + const auto sumHeight = top.height() + bottom.height(); + lockedLabelTop->moveToLeft( + (b.width() - top.width()) / 2, + factor * (b.height() - sumHeight) / 2); + lockedLabelBottom->moveToLeft( + (b.width() - bottom.width()) / 2, + factor * ((b.height() - sumHeight) / 2 + top.height())); + }, lockedLabelTop->lifetime()); + + const auto dateUpdateLifetime + = lockedLabelBottom->lifetime().make_state(); + std::move( + dateValue + ) | rpl::start_with_next([=](const QDateTime &dt) { + dateUpdateLifetime->destroy(); + if (dt.isNull()) { + return; + } + constexpr auto kDateUpdateInterval = crl::time(250); + const auto was = base::unixtime::serialize(dt); + + const auto context = Core::MarkedTextContext{ + .customEmojiRepaint = [=] { lockedLabelBottom->update(); }, + .session = session, + }; + const auto emoji = Ui::Text::SingleCustomEmoji( + session->data().customEmojiManager().registerInternalEmoji( + st::chatSimilarLockedIcon, + st::botEarnButtonLockMargins, + true)); + + rpl::single( + rpl::empty + ) | rpl::then( + base::timer_each(kDateUpdateInterval) + ) | rpl::start_with_next([=] { + const auto secondsDifference = std::max( + was - base::unixtime::now() - 1, + 0); + const auto hours = secondsDifference / 3600; + const auto minutes = (secondsDifference % 3600) / 60; + const auto seconds = secondsDifference % 60; + constexpr auto kZero = QChar('0'); + const auto formatted = (hours > 0) + ? (u"%1:%2:%3"_q) + .arg(hours, 2, 10, kZero) + .arg(minutes, 2, 10, kZero) + .arg(seconds, 2, 10, kZero) + : (u"%1:%2"_q) + .arg(minutes, 2, 10, kZero) + .arg(seconds, 2, 10, kZero); + lockedLabelBottom->setMarkedText( + base::duplicate(emoji).append(formatted), + context); + }, *dateUpdateLifetime); + }, lockedLabelBottom->lifetime()); + Api::HandleWithdrawalButton( Api::RewardReceiver{ .creditsReceiver = _peer, diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/channel_earn.style b/Telegram/SourceFiles/info/channel_statistics/earn/channel_earn.style index 11cd41680..ab88fa2ba 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/channel_earn.style +++ b/Telegram/SourceFiles/info/channel_statistics/earn/channel_earn.style @@ -139,6 +139,13 @@ sponsoredReportLabel: FlatLabel(defaultFlatLabel) { botEarnInputField: InputField(defaultInputField) { textMargins: margins(23px, 28px, 0px, 4px); + placeholderMargins: margins(-23px, 0px, 0px, 0px); width: 100px; heightMax: 55px; } +botEarnLockedButtonLabel: FlatLabel(channelEarnOverviewMajorLabel) { + style: TextStyle(defaultTextStyle) { + font: font(10px semibold); + } +} +botEarnButtonLockMargins: margins(-2px, 4px, 0px, 0px); From 712c06756ee25a525b1fe6c93d00be251a597694 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 20 Jun 2024 20:04:19 +0300 Subject: [PATCH 068/142] Added icon to label in withdrawal button from bot earn section. --- .../info/bot/earn/info_earn_inner_widget.cpp | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp index 2ba9e6b04..4e80c750a 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp @@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_channel_earn.h" #include "styles/style_chat.h" #include "styles/style_layers.h" +#include "styles/style_settings.h" #include "styles/style_statistics.h" namespace Info::BotEarn { @@ -343,6 +344,36 @@ void InnerWidget::fill() { button, tr::lng_channel_earn_balance_button(tr::now), st::channelEarnSemiboldLabel); + { + const auto buttonEmoji = Ui::Text::SingleCustomEmoji( + session->data().customEmojiManager().registerInternalEmoji( + st::settingsPremiumIconStar, + { 0, -st::moderateBoxExpandInnerSkip, 0, 0 }, + true)); + const auto context = Core::MarkedTextContext{ + .customEmojiRepaint = [=] { label->update(); }, + .session = session, + }; + const auto process = [=] { + const auto amount = input->getLastText().toDouble(); + if (amount >= _state.availableBalance) { + label->setText( + tr::lng_bot_earn_balance_button_all(tr::now)); + } else { + label->setMarkedText( + tr::lng_bot_earn_balance_button( + tr::now, + lt_count, + amount, + lt_emoji, + buttonEmoji, + Ui::Text::RichLangValue), + context); + } + }; + QObject::connect(input, &Ui::MaskedInputField::changed, process); + process(); + } label->setTextColorOverride(stButton.textFg->c); label->setAttribute(Qt::WA_TransparentForMouseEvents); rpl::combine( From d0a030ab5809a5d23a0283716e0a6ecee6074815 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 20 Jun 2024 20:42:56 +0300 Subject: [PATCH 069/142] Added support of minimal amount of credits to withdrawal button. --- Telegram/Resources/langs/lang.strings | 3 ++ Telegram/SourceFiles/api/api_earn.cpp | 3 ++ .../info/bot/earn/info_earn_inner_widget.cpp | 39 +++++++++++++++++-- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 02a263f42..d34b064f3 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -5203,6 +5203,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_earn_out_ph" = "Enter amount to withdraw"; "lng_bot_earn_balance_password_title" = "Two-step verification"; "lng_bot_earn_balance_password_description" = "Please enter your password to collect."; +"lng_bot_earn_credits_out_minimal" = "You cannot withdraw less then {link}."; +"lng_bot_earn_credits_out_minimal_link#one" = "{count} star"; +"lng_bot_earn_credits_out_minimal_link#other" = "{count} stars"; "lng_contact_add" = "Add"; "lng_contact_send_message" = "message"; diff --git a/Telegram/SourceFiles/api/api_earn.cpp b/Telegram/SourceFiles/api/api_earn.cpp index 724fbc28c..4997fb0ec 100644 --- a/Telegram/SourceFiles/api/api_earn.cpp +++ b/Telegram/SourceFiles/api/api_earn.cpp @@ -55,6 +55,9 @@ void HandleWithdrawalButton( if (state->loading) { return; } + if (peer && !receiver.creditsAmount()) { + return; + } state->loading = true; state->lifetime = session->api().cloudPassword().state( ) | rpl::take( diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp index 4e80c750a..43eabbb0d 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "core/ui_integration.h" #include "data/data_channel_earn.h" +#include "ui/toast/toast.h" #include "data/data_session.h" #include "data/data_user.h" #include "data/stickers/data_custom_emoji.h" @@ -23,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/statistics/info_statistics_inner_widget.h" // FillLoading. #include "lang/lang_keys.h" #include "main/main_account.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "statistics/chart_widget.h" #include "ui/effects/credits_graphics.h" @@ -43,6 +45,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Info::BotEarn { namespace { +[[nodiscard]] int WithdrawalMin(not_null session) { + const auto key = u"stars_revenue_withdrawal_min"_q; + return session->appConfig().get(key, 1000); +} + void AddHeader( not_null content, tr::phrase<> text) { @@ -344,7 +351,7 @@ void InnerWidget::fill() { button, tr::lng_channel_earn_balance_button(tr::now), st::channelEarnSemiboldLabel); - { + const auto processInputChange = [&] { const auto buttonEmoji = Ui::Text::SingleCustomEmoji( session->data().customEmojiManager().registerInternalEmoji( st::settingsPremiumIconStar, @@ -373,7 +380,8 @@ void InnerWidget::fill() { }; QObject::connect(input, &Ui::MaskedInputField::changed, process); process(); - } + return process; + }(); label->setTextColorOverride(stButton.textFg->c); label->setAttribute(Qt::WA_TransparentForMouseEvents); rpl::combine( @@ -470,7 +478,32 @@ void InnerWidget::fill() { Api::HandleWithdrawalButton( Api::RewardReceiver{ .creditsReceiver = _peer, - .creditsAmount = [=] { return input->getLastText().toInt(); }, + .creditsAmount = [=, show = _controller->uiShow()] { + const auto amount = input->getLastText().toULongLong(); + const auto min = float64(WithdrawalMin(session)); + if (amount <= min) { + auto text = tr::lng_bot_earn_credits_out_minimal( + tr::now, + lt_link, + Ui::Text::Link( + tr::lng_bot_earn_credits_out_minimal_link( + tr::now, + lt_count, + min), + u"internal:"_q), + Ui::Text::RichLangValue); + show->showToast(Ui::Toast::Config{ + .text = std::move(text), + .filter = [=](const auto ...) { + input->setText(QString::number(min)); + processInputChange(); + return true; + }, + }); + return 0ULL; + } + return amount; + }, }, button, _controller->uiShow()); From 7d115b3fab49f896aaa99d4b46c7c613aa969981 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 21 Jun 2024 02:49:14 +0300 Subject: [PATCH 070/142] Improved api support to credit history entries. --- Telegram/SourceFiles/api/api_credits.cpp | 21 ++++++++++++++++----- Telegram/SourceFiles/data/data_credits.h | 6 ++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 13cf45474..4d296286b 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -30,6 +30,12 @@ constexpr auto kTransactionsLimit = 100; const auto photo = tl.data().vphoto() ? peer->owner().photoFromWeb(*tl.data().vphoto(), ImageLocation()) : nullptr; + const auto barePeerId = tl.data().vpeer().match([]( + const HistoryPeerTL &p) { + return peerFromMTP(p.vpeer()); + }, [](const auto &) { + return PeerId(0); + }).value; return Data::CreditsHistoryEntry{ .id = qs(tl.data().vid()), .title = qs(tl.data().vtitle().value_or_empty()), @@ -37,11 +43,7 @@ constexpr auto kTransactionsLimit = 100; .date = base::unixtime::parse(tl.data().vdate().v), .photoId = photo ? photo->id : 0, .credits = tl.data().vstars().v, - .bareId = tl.data().vpeer().match([](const HistoryPeerTL &p) { - return peerFromMTP(p.vpeer()); - }, [](const auto &) { - return PeerId(0); - }).value, + .bareId = barePeerId, .peerType = tl.data().vpeer().match([](const HistoryPeerTL &) { return Data::CreditsHistoryEntry::PeerType::Peer; }, [](const MTPDstarsTransactionPeerPlayMarket &) { @@ -56,6 +58,15 @@ constexpr auto kTransactionsLimit = 100; return Data::CreditsHistoryEntry::PeerType::PremiumBot; }), .refunded = tl.data().is_refund(), + .pending = tl.data().is_pending(), + .failed = tl.data().is_failed(), + .finishDate = tl.data().vtransaction_date() + ? base::unixtime::parse(tl.data().vtransaction_date()->v) + : QDateTime(), + .finishUrl = qs(tl.data().vtransaction_url().value_or_empty()), + .in = (!barePeerId || tl.data().is_refund()) + && !tl.data().is_pending() + && !tl.data().is_failed(), }; } diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index ddfd22a83..f013382e8 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -38,6 +38,12 @@ struct CreditsHistoryEntry final { uint64 bareId = 0; PeerType peerType; bool refunded = false; + bool pending = false; + bool failed = false; + QDateTime finishDate; + QString finishUrl; + bool in = false; + }; struct CreditsStatusSlice final { From b5bd0f53ad7e3a30e63c8f4f41d84b3c5e785ba9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 21 Jun 2024 03:09:22 +0300 Subject: [PATCH 071/142] Added support of new statuses of credits history entries. --- Telegram/Resources/langs/lang.strings | 1 + .../info_statistics_list_controllers.cpp | 10 ++- .../settings/settings_credits_graphics.cpp | 65 ++++++++++--------- 3 files changed, 45 insertions(+), 31 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index d34b064f3..17bc96f9c 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -5164,6 +5164,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_channel_earn_history_return" = "Refund"; "lng_channel_earn_history_return_about" = "Refunded back"; "lng_channel_earn_history_pending" = "Pending"; +"lng_channel_earn_history_failed" = "Failed"; "lng_channel_earn_history_show_more#one" = "Show {count} More Transaction"; "lng_channel_earn_history_show_more#other" = "Show {count} More Transactions"; "lng_channel_earn_off" = "Switch Off Ads"; diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index f319f873b..92920d2fc 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -782,13 +782,17 @@ void CreditsRow::init() { langDateTimeFull(_entry.date) + (_entry.refunded ? (joiner + tr::lng_channel_earn_history_return(tr::now)) + : _entry.pending + ? (joiner + tr::lng_channel_earn_history_pending(tr::now)) + : _entry.failed + ? (joiner + tr::lng_channel_earn_history_failed(tr::now)) : QString()) + (_entry.title.isEmpty() ? QString() : (joiner + _name))); { constexpr auto kMinus = QChar(0x2212); _rightText.setText( st::semiboldTextStyle, - ((!_entry.bareId || _entry.refunded) ? QChar('+') : kMinus) + (_entry.in ? QChar('+') : kMinus) + Lang::FormatCountDecimal(std::abs(int64(_entry.credits)))); } if (!_paintUserpicCallback) { @@ -836,7 +840,9 @@ void CreditsRow::rightActionPaint( bool actionSelected) { const auto &font = _rightText.style()->font; y += _rowHeight / 2; - p.setPen((!_entry.bareId || _entry.refunded) + p.setPen(_entry.pending + ? st::creditsStroke + : _entry.in ? st::boxTextFgGood : st::menuIconAttentionColor); x += st::creditsHistoryRightSkip; diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index a8aab17c3..4eec43924 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -389,14 +389,19 @@ void ReceiptCreditsBox( auto &lifetime = content->lifetime(); const auto text = lifetime.make_state( st::semiboldTextStyle, - ((!e.bareId || e.refunded) ? QChar('+') : kMinus) + (e.in ? QChar('+') : kMinus) + Lang::FormatCountDecimal(std::abs(int64(e.credits)))); - const auto refundedText = tr::lng_channel_earn_history_return( - tr::now); - const auto refunded = e.refunded + const auto roundedText = e.refunded + ? tr::lng_channel_earn_history_return(tr::now) + : e.pending + ? tr::lng_channel_earn_history_pending(tr::now) + : e.failed + ? tr::lng_channel_earn_history_failed(tr::now) + : QString(); + const auto rounded = !roundedText.isEmpty() ? lifetime.make_state( st::defaultTextStyle, - refundedText) + roundedText) : (Ui::Text::String*)(nullptr); const auto amount = content->add( @@ -404,23 +409,25 @@ void ReceiptCreditsBox( content, star.height() / style::DevicePixelRatio())); const auto font = text->style()->font; - const auto refundedFont = st::defaultTextStyle.font; + const auto roundedFont = st::defaultTextStyle.font; const auto starWidth = star.width() / style::DevicePixelRatio(); - const auto refundedSkip = refundedFont->spacew * 2; - const auto refundedWidth = refunded - ? refundedFont->width(refundedText) - + refundedSkip - + refundedFont->height + const auto roundedSkip = roundedFont->spacew * 2; + const auto roundedWidth = rounded + ? roundedFont->width(roundedText) + + roundedSkip + + roundedFont->height : 0; const auto fullWidth = text->maxWidth() + font->spacew * 1 + starWidth - + refundedWidth; + + roundedWidth; amount->paintRequest( ) | rpl::start_with_next([=] { auto p = Painter(amount); - p.setPen((!e.bareId || e.refunded) + p.setPen(e.pending + ? st::creditsStroke + : e.in ? st::boxTextFgGood : st::menuIconAttentionColor); const auto x = (amount->width() - fullWidth) / 2; @@ -432,15 +439,15 @@ void ReceiptCreditsBox( .availableWidth = amount->width(), }); p.drawImage( - x + fullWidth - starWidth - refundedWidth, + x + fullWidth - starWidth - roundedWidth, 0, star); - if (refunded) { - const auto refundedLeft = fullWidth + if (rounded) { + const auto roundedLeft = fullWidth + x - - refundedWidth - + refundedSkip; + - roundedWidth + + roundedSkip; const auto pen = p.pen(); auto color = pen.color(); color.setAlphaF(color.alphaF() * 0.15); @@ -449,20 +456,20 @@ void ReceiptCreditsBox( { auto hq = PainterHighQualityEnabler(p); p.drawRoundedRect( - refundedLeft, - (amount->height() - refundedFont->height) / 2, - refundedWidth - refundedSkip, - refundedFont->height, - refundedFont->height / 2, - refundedFont->height / 2); + roundedLeft, + (amount->height() - roundedFont->height) / 2, + roundedWidth - roundedSkip, + roundedFont->height, + roundedFont->height / 2, + roundedFont->height / 2); } p.setPen(pen); - refunded->draw(p, Ui::Text::PaintContext{ + rounded->draw(p, Ui::Text::PaintContext{ .position = QPoint( - refundedLeft + refundedFont->height / 2, - (amount->height() - refundedFont->height) / 2), - .outerWidth = refundedWidth, - .availableWidth = refundedWidth, + roundedLeft + roundedFont->height / 2, + (amount->height() - roundedFont->height) / 2), + .outerWidth = roundedWidth, + .availableWidth = roundedWidth, }); } }, amount->lifetime()); From 3552da5ce7887863f889059356c27e3dbf6863b1 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 21 Jun 2024 04:26:50 +0300 Subject: [PATCH 072/142] Added additional row in table for some types of credits history entries. --- Telegram/Resources/langs/lang.strings | 4 ++++ .../SourceFiles/boxes/gift_premium_box.cpp | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 17bc96f9c..0043c59b3 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2334,6 +2334,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_summary_in_toast_about#one" = "**{count}** Star added to your balance."; "lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance."; "lng_credits_box_history_entry_peer" = "Recipient"; +"lng_credits_box_history_entry_via" = "Via"; +"lng_credits_box_history_entry_play_market" = "Play Market"; +"lng_credits_box_history_entry_app_store" = "App Store"; +"lng_credits_box_history_entry_fragment" = "Fragment"; "lng_credits_box_history_entry_id" = "Transaction ID"; "lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard."; "lng_credits_box_history_entry_about" = "You can dispute this transaction {link}."; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 251b94b75..150747663 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -1653,6 +1653,26 @@ void AddCreditsHistoryEntryTable( controller, PeerId(entry.bareId)); } + using Type = Data::CreditsHistoryEntry::PeerType; + if (entry.peerType == Type::AppStore) { + AddTableRow( + table, + tr::lng_credits_box_history_entry_via(), + tr::lng_credits_box_history_entry_app_store( + Ui::Text::RichLangValue)); + } else if (entry.peerType == Type::PlayMarket) { + AddTableRow( + table, + tr::lng_credits_box_history_entry_via(), + tr::lng_credits_box_history_entry_play_market( + Ui::Text::RichLangValue)); + } else if (entry.peerType == Type::Fragment) { + AddTableRow( + table, + tr::lng_credits_box_history_entry_via(), + tr::lng_credits_box_history_entry_fragment( + Ui::Text::RichLangValue)); + } if (!entry.id.isEmpty()) { constexpr auto kOneLineCount = 18; const auto oneLine = entry.id.length() <= kOneLineCount; From ebf456abe42dcb348e37fb307dcdc43b102ee48e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 21 Jun 2024 04:50:02 +0300 Subject: [PATCH 073/142] Added initial support of lists for credits history entries in bot earn. --- .../info/bot/earn/info_earn_inner_widget.cpp | 187 +++++++++++++++++- .../info/bot/earn/info_earn_inner_widget.h | 1 + 2 files changed, 187 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp index 43eabbb0d..fa4cbe348 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp @@ -14,7 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "core/ui_integration.h" #include "data/data_channel_earn.h" -#include "ui/toast/toast.h" #include "data/data_session.h" #include "data/data_user.h" #include "data/stickers/data_custom_emoji.h" @@ -22,22 +21,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/channel_statistics/earn/earn_format.h" #include "info/info_controller.h" #include "info/statistics/info_statistics_inner_widget.h" // FillLoading. +#include "info/statistics/info_statistics_list_controllers.h" #include "lang/lang_keys.h" #include "main/main_account.h" #include "main/main_app_config.h" #include "main/main_session.h" +#include "settings/settings_credits_graphics.h" #include "statistics/chart_widget.h" +#include "statistics/widgets/chart_header_widget.h" #include "ui/effects/credits_graphics.h" +#include "ui/layers/generic_box.h" #include "ui/rect.h" #include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/discrete_sliders.h" #include "ui/widgets/fields/number_input.h" #include "ui/widgets/label_with_custom_emoji.h" #include "ui/widgets/labels.h" +#include "ui/wrap/slide_wrap.h" #include "styles/style_boxes.h" #include "styles/style_channel_earn.h" #include "styles/style_chat.h" +#include "styles/style_credits.h" #include "styles/style_layers.h" #include "styles/style_settings.h" #include "styles/style_statistics.h" @@ -542,6 +549,184 @@ void InnerWidget::fill() { Ui::AddSkip(container); } + + fillHistory(); +} + +void InnerWidget::fillHistory() { + const auto container = this; + const auto history = container->add( + object_ptr>( + container, + object_ptr(container))); + const auto content = history->entity(); + + Ui::AddSkip(content, st::settingsPremiumOptionsPadding.top()); + + const auto fill = [=]( + not_null premiumBot, + const Data::CreditsStatusSlice &fullSlice, + const Data::CreditsStatusSlice &inSlice, + const Data::CreditsStatusSlice &outSlice) { + const auto inner = content; + if (fullSlice.list.empty()) { + return; + } + const auto hasOneTab = inSlice.list.empty() && outSlice.list.empty(); + const auto hasIn = !inSlice.list.empty(); + const auto hasOut = !outSlice.list.empty(); + const auto fullTabText = tr::lng_credits_summary_history_tab_full( + tr::now); + const auto inTabText = tr::lng_credits_summary_history_tab_in( + tr::now); + const auto outTabText = tr::lng_credits_summary_history_tab_out( + tr::now); + if (hasOneTab) { + Ui::AddSkip(inner); + const auto header = inner->add( + object_ptr(inner), + st::statisticsLayerMargins + + st::boostsChartHeaderPadding); + header->resizeToWidth(header->width()); + header->setTitle(fullTabText); + header->setSubTitle({}); + } + + class Slider final : public Ui::SettingsSlider { + public: + using Ui::SettingsSlider::SettingsSlider; + void setNaturalWidth(int w) { + _naturalWidth = w; + } + int naturalWidth() const override { + return _naturalWidth; + } + + private: + int _naturalWidth = 0; + + }; + + const auto slider = inner->add( + object_ptr>( + inner, + object_ptr(inner, st::defaultTabsSlider)), + st::boxRowPadding); + slider->toggle(!hasOneTab, anim::type::instant); + + slider->entity()->addSection(fullTabText); + if (hasIn) { + slider->entity()->addSection(inTabText); + } + if (hasOut) { + slider->entity()->addSection(outTabText); + } + + { + const auto &st = st::defaultTabsSlider; + slider->entity()->setNaturalWidth(0 + + st.labelStyle.font->width(fullTabText) + + (hasIn ? st.labelStyle.font->width(inTabText) : 0) + + (hasOut ? st.labelStyle.font->width(outTabText) : 0) + + rect::m::sum::h(st::boxRowPadding)); + } + + const auto fullWrap = inner->add( + object_ptr>( + inner, + object_ptr(inner))); + const auto inWrap = inner->add( + object_ptr>( + inner, + object_ptr(inner))); + const auto outWrap = inner->add( + object_ptr>( + inner, + object_ptr(inner))); + + rpl::single(0) | rpl::then( + slider->entity()->sectionActivated() + ) | rpl::start_with_next([=](int index) { + if (index == 0) { + fullWrap->toggle(true, anim::type::instant); + inWrap->toggle(false, anim::type::instant); + outWrap->toggle(false, anim::type::instant); + } else if (index == 1) { + inWrap->toggle(true, anim::type::instant); + fullWrap->toggle(false, anim::type::instant); + outWrap->toggle(false, anim::type::instant); + } else { + outWrap->toggle(true, anim::type::instant); + fullWrap->toggle(false, anim::type::instant); + inWrap->toggle(false, anim::type::instant); + } + }, inner->lifetime()); + + const auto controller = _controller->parentController(); + const auto entryClicked = [=](const Data::CreditsHistoryEntry &e) { + controller->uiShow()->show(Box( + ::Settings::ReceiptCreditsBox, + controller, + premiumBot.get(), + e)); + }; + + const auto star = lifetime().make_state( + Ui::GenerateStars(st::creditsTopupButton.height, 1)); + + Info::Statistics::AddCreditsHistoryList( + controller->uiShow(), + fullSlice, + fullWrap->entity(), + entryClicked, + premiumBot, + star, + true, + true); + Info::Statistics::AddCreditsHistoryList( + controller->uiShow(), + inSlice, + inWrap->entity(), + entryClicked, + premiumBot, + star, + true, + false); + Info::Statistics::AddCreditsHistoryList( + controller->uiShow(), + outSlice, + outWrap->entity(), + std::move(entryClicked), + premiumBot, + star, + false, + true); + + Ui::AddSkip(inner); + Ui::AddSkip(inner); + + inner->resizeToWidth(container->width()); + }; + + const auto apiLifetime = content->lifetime().make_state(); + { + using Api = Api::CreditsHistory; + const auto apiFull = apiLifetime->make_state(_peer, true, true); + const auto apiIn = apiLifetime->make_state(_peer, true, false); + const auto apiOut = apiLifetime->make_state(_peer, false, true); + apiFull->request({}, [=](Data::CreditsStatusSlice fullSlice) { + apiIn->request({}, [=](Data::CreditsStatusSlice inSlice) { + apiOut->request({}, [=](Data::CreditsStatusSlice outSlice) { + ::Api::PremiumPeerBot( + &_controller->session() + ) | rpl::start_with_next([=](not_null bot) { + fill(bot, fullSlice, inSlice, outSlice); + apiLifetime->destroy(); + }, *apiLifetime); + }); + }); + }); + } } void InnerWidget::saveState(not_null memento) { diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h index b3bc5655a..65558b3e2 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h @@ -51,6 +51,7 @@ public: private: void load(); void fill(); + void fillHistory(); not_null _controller; not_null _peer; From e71a067f4bf7f650e9fdf483832529a78125352c Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 21 Jun 2024 11:54:58 +0400 Subject: [PATCH 074/142] Fix build on Windows. --- Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp index fa4cbe348..026cacb08 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp @@ -365,8 +365,8 @@ void InnerWidget::fill() { { 0, -st::moderateBoxExpandInnerSkip, 0, 0 }, true)); const auto context = Core::MarkedTextContext{ - .customEmojiRepaint = [=] { label->update(); }, .session = session, + .customEmojiRepaint = [=] { label->update(); }, }; const auto process = [=] { const auto amount = input->getLastText().toDouble(); @@ -447,8 +447,8 @@ void InnerWidget::fill() { const auto was = base::unixtime::serialize(dt); const auto context = Core::MarkedTextContext{ - .customEmojiRepaint = [=] { lockedLabelBottom->update(); }, .session = session, + .customEmojiRepaint = [=] { lockedLabelBottom->update(); }, }; const auto emoji = Ui::Text::SingleCustomEmoji( session->data().customEmojiManager().registerInternalEmoji( From 3ece9b1566b4845e070df1b1fece3f760738e6a7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 18 Jun 2024 14:02:05 +0400 Subject: [PATCH 075/142] Update API scheme to layer 183. Paid media. --- Telegram/CMakeLists.txt | 2 - Telegram/SourceFiles/api/api_updates.cpp | 2 +- .../SourceFiles/data/data_media_types.cpp | 116 ++++-- Telegram/SourceFiles/data/data_media_types.h | 25 +- Telegram/SourceFiles/data/data_photo.cpp | 36 ++ Telegram/SourceFiles/data/data_photo.h | 18 +- Telegram/SourceFiles/data/data_session.cpp | 3 +- .../export/data/export_data_types.cpp | 24 ++ .../export/data/export_data_types.h | 13 + .../export/output/export_output_html.cpp | 3 + .../export/output/export_output_json.cpp | 2 + .../history/history_inner_widget.cpp | 2 +- Telegram/SourceFiles/history/history_item.cpp | 15 +- Telegram/SourceFiles/history/history_item.h | 4 +- .../history/history_item_components.cpp | 4 +- .../history/history_item_helpers.cpp | 2 + .../history/view/history_view_list_widget.cpp | 2 +- .../view/history_view_scheduled_section.cpp | 2 +- .../media/history_view_extended_preview.cpp | 366 ------------------ .../media/history_view_extended_preview.h | 87 ----- .../history/view/media/history_view_photo.cpp | 12 +- .../media/view/media_view_overlay_widget.cpp | 6 +- Telegram/SourceFiles/mtproto/scheme/api.tl | 8 +- .../business/settings_shortcut_messages.cpp | 2 +- .../ui/controls/userpic_button.cpp | 4 +- 25 files changed, 227 insertions(+), 533 deletions(-) delete mode 100644 Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp delete mode 100644 Telegram/SourceFiles/history/view/media/history_view_extended_preview.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index fc0f622fc..2b41b09fc 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -727,8 +727,6 @@ PRIVATE history/view/media/history_view_dice.h history/view/media/history_view_document.cpp history/view/media/history_view_document.h - history/view/media/history_view_extended_preview.cpp - history/view/media/history_view_extended_preview.h history/view/media/history_view_file.cpp history/view/media/history_view_file.h history/view/media/history_view_game.cpp diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 0ebf17f33..2d7207bae 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -1696,7 +1696,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { const auto peerId = peerFromMTP(d.vpeer()); const auto msgId = d.vmsg_id().v; if (const auto item = session().data().message(peerId, msgId)) { - item->applyEdition(d.vextended_media()); + item->applyEdition(d.vextended_media().v); } } break; diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 4594b7fa2..e076438d7 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -7,12 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_media_types.h" +#include "base/random.h" #include "history/history.h" #include "history/history_item.h" // CreateMedia. #include "history/history_location_manager.h" #include "history/view/history_view_element.h" #include "history/view/history_view_item_preview.h" -#include "history/view/media/history_view_extended_preview.h" #include "history/view/media/history_view_photo.h" #include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_gif.h" @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_giveaway.h" #include "history/view/media/history_view_invoice.h" #include "history/view/media/history_view_media_generic.h" +#include "history/view/media/history_view_media_grouped.h" #include "history/view/media/history_view_call.h" #include "history/view/media/history_view_web_page.h" #include "history/view/media/history_view_poll.h" @@ -261,48 +262,80 @@ template } bool UpdateExtendedMedia( - Invoice &invoice, + std::unique_ptr &media, not_null item, - const MTPMessageExtendedMedia &media) { - return media.match([&](const MTPDmessageExtendedMediaPreview &data) { - if (invoice.extendedMedia) { - return false; + const MTPMessageExtendedMedia &extended) { + return extended.match([&](const MTPDmessageExtendedMediaPreview &data) { + auto photo = (PhotoData*)nullptr; + if (!media) { + const auto id = base::RandomValue(); + photo = item->history()->owner().photo(id); + } else { + photo = media->photo(); + if (!photo || !photo->extendedMediaPreview()) { + return false; + } } + auto changed = false; - auto &preview = invoice.extendedPreview; + auto size = QSize(); + auto thumbnail = QByteArray(); + auto videoDuration = TimeId(); if (const auto &w = data.vw()) { const auto &h = data.vh(); Assert(h.has_value()); - const auto dimensions = QSize(w->v, h->v); - if (preview.dimensions != dimensions) { - preview.dimensions = dimensions; + size = QSize(w->v, h->v); + if (!changed && photo->size(PhotoSize::Large) != size) { changed = true; } } if (const auto &thumb = data.vthumb()) { if (thumb->type() == mtpc_photoStrippedSize) { - const auto bytes = thumb->c_photoStrippedSize().vbytes().v; - if (preview.inlineThumbnailBytes != bytes) { - preview.inlineThumbnailBytes = bytes; + thumbnail = thumb->c_photoStrippedSize().vbytes().v; + if (!changed && photo->inlineThumbnailBytes() != thumbnail) { changed = true; } } } if (const auto &duration = data.vvideo_duration()) { - if (preview.videoDuration != duration->v) { - preview.videoDuration = duration->v; + videoDuration = duration->v; + if (photo->extendedMediaVideoDuration() != videoDuration) { changed = true; } } + if (changed) { + photo->setExtendedMediaPreview(size, thumbnail, videoDuration); + } + if (!media) { + media = std::make_unique(item, photo, true); + } return changed; }, [&](const MTPDmessageExtendedMedia &data) { - invoice.extendedMedia = HistoryItem::CreateMedia( - item, - data.vmedia()); + media = HistoryItem::CreateMedia(item, data.vmedia()); return true; }); } +bool UpdateExtendedMedia( + Invoice &invoice, + not_null item, + const QVector &media) { + auto changed = false; + const auto count = int(media.size()); + for (auto i = 0; i != count; ++i) { + if (i < invoice.extendedMedia.size()) { + invoice.extendedMedia.emplace_back(); + changed = true; + } + UpdateExtendedMedia(invoice.extendedMedia[i], item, media[i]); + } + if (count < invoice.extendedMedia.size()) { + invoice.extendedMedia.resize(count); + changed = true; + } + return changed; +} + TextForMimeData WithCaptionClipboardText( const QString &attachType, TextForMimeData &&caption) { @@ -344,11 +377,22 @@ Invoice ComputeInvoiceData( .isTest = data.is_test(), }; if (const auto &media = data.vextended_media()) { - UpdateExtendedMedia(result, item, *media); + UpdateExtendedMedia(result, item, { *media }); } return result; } +Invoice ComputeInvoiceData( + not_null item, + const MTPDmessageMediaPaidMedia &data) { + auto result = Invoice{ + .amount = data.vstars_amount().v, + .currency = Ui::kCreditsCurrency, + }; + UpdateExtendedMedia(result, item, data.vextended_media().v); + return result; +} + Call ComputeCallData(const MTPDmessageActionPhoneCall &call) { auto result = Call(); result.finishReason = [&] { @@ -424,6 +468,18 @@ GiveawayResults ComputeGiveawayResultsData( return result; } +bool HasExtendedMedia(const Invoice &invoice) { + return !invoice.extendedMedia.empty(); +} + +bool HasUnpaidMedia(const Invoice &invoice) { + for (const auto &media : invoice.extendedMedia) { + const auto photo = media->photo(); + return photo && photo->extendedMediaPreview(); + } + return false; +} + Media::Media(not_null parent) : _parent(parent) { } @@ -1851,14 +1907,14 @@ MediaInvoice::MediaInvoice( .currency = data.currency, .title = data.title, .description = data.description, - .extendedPreview = data.extendedPreview, - .extendedMedia = (data.extendedMedia - ? data.extendedMedia->clone(parent) - : nullptr), .photo = data.photo, .isTest = data.isTest, } { - if (_invoice.extendedPreview && !_invoice.extendedMedia) { + _invoice.extendedMedia.reserve(data.extendedMedia.size()); + for (auto &item : data.extendedMedia) { + _invoice.extendedMedia.push_back(item->clone(parent)); + } + if (HasUnpaidMedia(_invoice)) { Ui::PreloadImageSpoiler(); } } @@ -1917,7 +1973,7 @@ bool MediaInvoice::updateSentMedia(const MTPMessageMedia &media) { bool MediaInvoice::updateExtendedMedia( not_null item, - const MTPMessageExtendedMedia &media) { + const QVector &media) { Expects(item == parent()); return UpdateExtendedMedia(_invoice, item, media); @@ -1927,15 +1983,15 @@ std::unique_ptr MediaInvoice::createView( not_null message, not_null realParent, HistoryView::Element *replacing) { - if (_invoice.extendedMedia) { - return _invoice.extendedMedia->createView( + if (_invoice.extendedMedia.size() == 1) { + return _invoice.extendedMedia.front()->createView( message, realParent, replacing); - } else if (_invoice.extendedPreview) { - return std::make_unique( + } else if (!_invoice.extendedMedia.empty()) { + return std::make_unique( message, - &_invoice); + _invoice.extendedMedia); } return std::make_unique(message, &_invoice); } diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 08d1320b9..3e26877d4 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -84,19 +84,6 @@ struct Call { }; -struct ExtendedPreview { - QByteArray inlineThumbnailBytes; - QSize dimensions; - TimeId videoDuration = -1; - - [[nodiscard]] bool empty() const { - return dimensions.isEmpty(); - } - explicit operator bool() const { - return !empty(); - } -}; - class Media; struct Invoice { @@ -105,11 +92,12 @@ struct Invoice { QString currency; QString title; TextWithEntities description; - ExtendedPreview extendedPreview; - std::unique_ptr extendedMedia; + std::vector> extendedMedia; PhotoData *photo = nullptr; bool isTest = false; }; +[[nodiscard]] bool HasExtendedMedia(const Invoice &invoice); +[[nodiscard]] bool HasUnpaidMedia(const Invoice &invoice); struct GiveawayStart { std::vector> channels; @@ -207,7 +195,7 @@ public: virtual bool updateSentMedia(const MTPMessageMedia &media) = 0; virtual bool updateExtendedMedia( not_null item, - const MTPMessageExtendedMedia &media) { + const QVector &media) { return false; } virtual std::unique_ptr createView( @@ -524,7 +512,7 @@ public: bool updateSentMedia(const MTPMessageMedia &media) override; bool updateExtendedMedia( not_null item, - const MTPMessageExtendedMedia &media) override; + const QVector &media) override; std::unique_ptr createView( not_null message, not_null realParent, @@ -750,6 +738,9 @@ private: [[nodiscard]] Invoice ComputeInvoiceData( not_null item, const MTPDmessageMediaInvoice &data); +[[nodiscard]] Invoice ComputeInvoiceData( + not_null item, + const MTPDmessageMediaPaidMedia &data); [[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call); diff --git a/Telegram/SourceFiles/data/data_photo.cpp b/Telegram/SourceFiles/data/data_photo.cpp index 2530ccc75..35186750b 100644 --- a/Telegram/SourceFiles/data/data_photo.cpp +++ b/Telegram/SourceFiles/data/data_photo.cpp @@ -50,6 +50,38 @@ PhotoData::~PhotoData() { base::take(_videoSizes); } +void PhotoData::setFields(TimeId date, bool hasAttachedStickers) { + _dateOrExtendedVideoDuration = date; + _hasStickers = hasAttachedStickers; + _extendedMediaPreview = false; +} + +void PhotoData::setExtendedMediaPreview( + QSize dimensions, + const QByteArray &inlineThumbnailBytes, + TimeId videoDuration) { + _extendedMediaPreview = true; + updateImages( + inlineThumbnailBytes, + {}, + {}, + { .location = { {}, dimensions.width(), dimensions.height() } }, + {}, + {}, + {}); + _dateOrExtendedVideoDuration = videoDuration + 1; +} + +bool PhotoData::extendedMediaPreview() const { + return _extendedMediaPreview; +} + +std::optional PhotoData::extendedMediaVideoDuration() const { + return (_extendedMediaPreview && _dateOrExtendedVideoDuration) + ? TimeId(_dateOrExtendedVideoDuration - 1) + : std::optional(); +} + Data::Session &PhotoData::owner() const { return *_owner; } @@ -74,6 +106,10 @@ void PhotoData::load( load(PhotoSize::Large, origin, fromCloud, autoLoading); } +TimeId PhotoData::date() const { + return _extendedMediaPreview ? 0 : _dateOrExtendedVideoDuration; +} + bool PhotoData::loading() const { return loading(PhotoSize::Large); } diff --git a/Telegram/SourceFiles/data/data_photo.h b/Telegram/SourceFiles/data/data_photo.h index 33d990723..98166d265 100644 --- a/Telegram/SourceFiles/data/data_photo.h +++ b/Telegram/SourceFiles/data/data_photo.h @@ -53,6 +53,7 @@ public: void automaticLoadSettingsChanged(); + [[nodiscard]] TimeId date() const; [[nodiscard]] bool loading() const; [[nodiscard]] bool displayLoading() const; void cancel(); @@ -89,6 +90,14 @@ public: [[nodiscard]] auto activeMediaView() const -> std::shared_ptr; + void setFields(TimeId date, bool hasAttachedStickers); + void setExtendedMediaPreview( + QSize dimensions, + const QByteArray &inlineThumbnailBytes, + TimeId videoDuration); + [[nodiscard]] bool extendedMediaPreview() const; + [[nodiscard]] std::optional extendedMediaVideoDuration() const; + void updateImages( const QByteArray &inlineThumbnailBytes, const ImageWithLocation &small, @@ -148,11 +157,10 @@ public: void setHasAttachedStickers(bool value); // For now they return size of the 'large' image. - int width() const; - int height() const; + [[nodiscard]] int width() const; + [[nodiscard]] int height() const; PhotoId id = 0; - TimeId date = 0; PeerData *peer = nullptr; // for chat and channel photos connection // geo, caption @@ -164,6 +172,8 @@ private: [[nodiscard]] const Data::CloudFile &videoFile( Data::PhotoSize size) const; + TimeId _dateOrExtendedVideoDuration = 0; + struct VideoSizes { Data::CloudFile small; Data::CloudFile large; @@ -177,6 +187,8 @@ private: int32 _dc = 0; uint64 _access = 0; bool _hasStickers = false; + bool _extendedMediaPreview = false; + QByteArray _fileReference; std::unique_ptr _replyPreview; std::weak_ptr _media; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index b22126ebb..869476243 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -3077,8 +3077,7 @@ void Session::photoApplyFields( return; } photo->setRemoteLocation(dc, access, fileReference); - photo->date = date; - photo->setHasAttachedStickers(hasStickers); + photo->setFields(date, hasStickers); photo->updateImages( inlineThumbnailBytes, small, diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 71750a575..16dbfb994 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -666,6 +666,28 @@ Invoice ParseInvoice(const MTPDmessageMediaInvoice &data) { return result; } +PaidMedia ParsePaidMedia( + ParseMediaContext &context, + const MTPDmessageMediaPaidMedia &data, + const QString &folder, + TimeId date) { + auto result = PaidMedia(); + result.stars = data.vstars_amount().v; + result.extended.reserve(data.vextended_media().v.size()); + for (const auto &extended : data.vextended_media().v) { + result.extended.push_back(extended.match([]( + const MTPDmessageExtendedMediaPreview &) + -> std::unique_ptr { + return std::unique_ptr(); + }, [&](const MTPDmessageExtendedMedia &data) + -> std::unique_ptr { + return std::make_unique( + ParseMedia(context, data.vmedia(), folder, date)); + })); + } + return result; +} + Poll ParsePoll(const MTPDmessageMediaPoll &data) { auto result = Poll(); data.vpoll().match([&](const MTPDpoll &poll) { @@ -1225,6 +1247,8 @@ Media ParseMedia( result.content = ParseGiveaway(data); }, [&](const MTPDmessageMediaGiveawayResults &data) { // #TODO export giveaway + }, [&](const MTPDmessageMediaPaidMedia &data) { + result.content = ParsePaidMedia(context, data, folder, date); }, [](const MTPDmessageMediaEmpty &data) {}); return result; } diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 76585991c..cc16c47ba 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -182,6 +182,18 @@ struct Invoice { int32 receiptMsgId = 0; }; +struct Media; +struct PaidMedia { + PaidMedia() = default; + PaidMedia(PaidMedia &&) = default; + PaidMedia &operator=(PaidMedia &&) = default; + PaidMedia(const PaidMedia &) = delete; + PaidMedia &operator=(const PaidMedia &) = delete; + + uint64 stars = 0; + std::vector> extended; +}; + struct Poll { struct Answer { Utf8String text; @@ -337,6 +349,7 @@ struct Media { Invoice, Poll, GiveawayStart, + PaidMedia, UnsupportedMedia> content; TimeId ttl = 0; diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index 9df6bcbb3..f49776ede 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -2092,6 +2092,9 @@ MediaData HtmlWriter::Wrap::prepareMediaData( result.status = Data::FormatMoneyAmount(data.amount, data.currency); }, [](const Poll &data) { }, [](const GiveawayStart &data) { + }, [&](const PaidMedia &data) { + result.classes = "media_invoice"; + result.status = Data::FormatMoneyAmount(data.stars, "XTR"); }, [](const UnsupportedMedia &data) { Unexpected("Unsupported message."); }, [](v::null_t) {}); diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index b485e615b..adbad2b7f 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -779,6 +779,8 @@ QByteArray SerializeMessage( { "until_date", SerializeDate(data.untilDate) }, { "channels", serialized }, })); + }, [&](const PaidMedia &data) { + push("paid_stars_amount", data.stars); }, [](const UnsupportedMedia &data) { Unexpected("Unsupported message."); }, [](v::null_t) {}); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 46d1a4612..69268f05e 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -1031,7 +1031,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { } } session().data().reactions().poll(item, context.now); - if (item->hasExtendedMediaPreview()) { + if (item->hasUnpaidContent()) { session().api().views().pollExtendedMedia(item); } _reactionsManager->recordCurrentReactionEffect( diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 73e0662c0..da40ecec5 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -349,6 +349,10 @@ std::unique_ptr HistoryItem::CreateMedia( return std::make_unique( item, Data::ComputeGiveawayResultsData(item, media)); + }, [&](const MTPDmessageMediaPaidMedia &media) -> Result { + return std::make_unique( + item, + Data::ComputeInvoiceData(item, media)); }, [](const MTPDmessageMediaEmpty &) -> Result { return nullptr; }, [](const MTPDmessageMediaUnsupported &) -> Result { @@ -1813,7 +1817,8 @@ void HistoryItem::applyEdition(const MTPDmessageService &message) { } } -void HistoryItem::applyEdition(const MTPMessageExtendedMedia &media) { +void HistoryItem::applyEdition( + const QVector &media) { if (const auto existing = this->media()) { if (existing->updateExtendedMedia(this, media)) { checkBuyButton(); @@ -2241,7 +2246,7 @@ bool HistoryItem::forbidsSaving() const { if (forbidsForward()) { return true; } else if (const auto invoice = _media ? _media->invoice() : nullptr) { - return (invoice->extendedMedia != nullptr); + return HasExtendedMedia(*invoice); } return false; } @@ -2991,10 +2996,10 @@ bool HistoryItem::externalReply() const { return false; } -bool HistoryItem::hasExtendedMediaPreview() const { +bool HistoryItem::hasUnpaidContent() const { if (const auto media = _media.get()) { if (const auto invoice = media->invoice()) { - return (invoice->extendedPreview && !invoice->extendedMedia); + return HasUnpaidMedia(*invoice); } } return false; @@ -3778,7 +3783,7 @@ void HistoryItem::createComponents(const MTPDmessage &data) { void HistoryItem::refreshMedia(const MTPMessageMedia *media) { const auto was = (_media != nullptr); if (const auto invoice = was ? _media->invoice() : nullptr) { - if (invoice->extendedMedia) { + if (HasExtendedMedia(*invoice)) { return; } } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 3118bf3bc..bb23aa3fd 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -326,7 +326,7 @@ public: [[nodiscard]] int repliesCount() const; [[nodiscard]] bool repliesAreComments() const; [[nodiscard]] bool externalReply() const; - [[nodiscard]] bool hasExtendedMediaPreview() const; + [[nodiscard]] bool hasUnpaidContent() const; [[nodiscard]] bool inHighlightProcess() const; void highlightProcessDone(); @@ -345,7 +345,7 @@ public: void applyChanges(not_null story); void applyEdition(const MTPDmessageService &message); - void applyEdition(const MTPMessageExtendedMedia &media); + void applyEdition(const QVector &media); void updateForwardedInfo(const MTPMessageFwdHeader *fwd); void updateSentContent( const TextWithEntities &textWithEntities, diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index fd4ff1605..55e9e09a5 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -1091,8 +1091,8 @@ void HistoryMessageReplyMarkup::updateData( bool HistoryMessageReplyMarkup::hiddenBy(Data::Media *media) const { if (media && (data.flags & ReplyMarkupFlag::OnlyBuyButton)) { if (const auto invoice = media->invoice()) { - if (invoice->extendedPreview - && (!invoice->extendedMedia || !invoice->receiptMsgId)) { + if (HasUnpaidMedia(*invoice) + || (HasExtendedMedia(*invoice) && !invoice->receiptMsgId)) { return true; } } diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index d3d9569e6..9a6423bcc 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -557,6 +557,8 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) { return Result::Good; }, [](const MTPDmessageMediaGiveawayResults &) { return Result::Good; + }, [](const MTPDmessageMediaPaidMedia &) { + return Result::Good; }, [](const MTPDmessageMediaUnsupported &) { return Result::Unsupported; }); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index eb5e079ad..2c99c6343 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -2263,7 +2263,7 @@ void ListWidget::paintEvent(QPaintEvent *e) { } } session->data().reactions().poll(item, context.now); - if (item->hasExtendedMediaPreview()) { + if (item->hasUnpaidContent()) { session->api().views().pollExtendedMedia(item); } if (_reactionsManager) { diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 045b5b830..07ce6a82d 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -1399,7 +1399,7 @@ CopyRestrictionType ScheduledWidget::listCopyMediaRestrictionType( not_null item) { if (const auto media = item->media()) { if (const auto invoice = media->invoice()) { - if (invoice->extendedMedia) { + if (HasExtendedMedia(*invoice)) { return CopyMediaRestrictionTypeFor(_history->peer, item); } } diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp deleted file mode 100644 index 9c1a73a64..000000000 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp +++ /dev/null @@ -1,366 +0,0 @@ -/* -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 "history/view/media/history_view_extended_preview.h" - -#include "history/history_item.h" -#include "history/history.h" -#include "history/history_item_components.h" -#include "history/view/history_view_element.h" -#include "history/view/history_view_cursor_state.h" -#include "history/view/media/history_view_media_common.h" -#include "media/streaming/media_streaming_utility.h" -#include "ui/effects/spoiler_mess.h" -#include "ui/image/image.h" -#include "ui/image/image_prepare.h" -#include "ui/chat/chat_style.h" -#include "ui/painter.h" -#include "ui/power_saving.h" -#include "data/data_session.h" -#include "payments/payments_checkout_process.h" -#include "payments/payments_non_panel_process.h" -#include "window/window_session_controller.h" -#include "mainwindow.h" -#include "core/click_handler_types.h" -#include "styles/style_chat.h" - -namespace HistoryView { -namespace { - -[[nodiscard]] ClickHandlerPtr MakeInvoiceLink(not_null item) { - return std::make_shared([=](ClickContext context) { - const auto my = context.other.value(); - const auto controller = my.sessionWindow.get(); - Payments::CheckoutProcess::Start( - item, - Payments::Mode::Payment, - (controller - ? crl::guard( - controller, - [=](auto) { controller->widget()->activate(); }) - : Fn()), - (controller - ? Payments::ProcessNonPanelPaymentFormFactory( - controller, - item) - : nullptr)); - }); -} - -} // namespace - -ExtendedPreview::ExtendedPreview( - not_null parent, - not_null invoice) -: Media(parent) -, _invoice(invoice) { - const auto item = parent->data(); - _spoiler.link = MakeInvoiceLink(item); - resolveButtonText(); -} - -void ExtendedPreview::resolveButtonText() { - if (const auto markup = _parent->data()->inlineReplyMarkup()) { - for (const auto &row : markup->data.rows) { - for (const auto &button : row) { - if (button.type == HistoryMessageMarkupButton::Type::Buy) { - _buttonText.setText( - st::semiboldTextStyle, - TextUtilities::SingleLine(button.text)); - return; - } - } - } - } -} - -ExtendedPreview::~ExtendedPreview() { - if (hasHeavyPart()) { - unloadHeavyPart(); - _parent->checkHeavyPart(); - } -} - -void ExtendedPreview::ensureThumbnailRead() const { - if (!_inlineThumbnail.isNull() || _imageCacheInvalid) { - return; - } - const auto &bytes = _invoice->extendedPreview.inlineThumbnailBytes; - if (bytes.isEmpty()) { - return; - } - _inlineThumbnail = Images::FromInlineBytes(bytes); - if (_inlineThumbnail.isNull()) { - _imageCacheInvalid = true; - } else { - history()->owner().registerHeavyViewPart(_parent); - } -} - -bool ExtendedPreview::hasHeavyPart() const { - return _spoiler.animation || !_inlineThumbnail.isNull(); -} - -void ExtendedPreview::unloadHeavyPart() { - _inlineThumbnail - = _spoiler.background - = _spoiler.cornerCache - = _buttonBackground = QImage(); - _spoiler.animation = nullptr; -} - -bool ExtendedPreview::enforceBubbleWidth() const { - return true; -} - -QSize ExtendedPreview::countOptimalSize() { - const auto &preview = _invoice->extendedPreview; - const auto dimensions = preview.dimensions; - const auto minWidth = std::min( - std::max({ - _parent->minWidthForMedia(), - (_parent->hasBubble() - ? st::historyPhotoBubbleMinWidth - : st::minPhotoSize), - minWidthForButton(), - }), - st::maxMediaSize); - const auto scaled = CountDesiredMediaSize(dimensions); - auto maxWidth = qMax(scaled.width(), minWidth); - auto minHeight = qMax(scaled.height(), st::minPhotoSize); - if (preview.videoDuration < 0) { - accumulate_max(maxWidth, scaled.height()); - } - return { maxWidth, minHeight }; -} - -QSize ExtendedPreview::countCurrentSize(int newWidth) { - const auto &preview = _invoice->extendedPreview; - const auto dimensions = preview.dimensions; - const auto thumbMaxWidth = std::min(newWidth, st::maxMediaSize); - const auto minWidth = std::min( - std::max({ - _parent->minWidthForMedia(), - (_parent->hasBubble() - ? st::historyPhotoBubbleMinWidth - : st::minPhotoSize), - minWidthForButton(), - }), - thumbMaxWidth); - const auto scaled = (preview.videoDuration >= 0) - ? CountMediaSize( - CountDesiredMediaSize(dimensions), - newWidth) - : CountPhotoMediaSize( - CountDesiredMediaSize(dimensions), - newWidth, - maxWidth()); - newWidth = qMax(scaled.width(), minWidth); - auto newHeight = qMax(scaled.height(), st::minPhotoSize); - if (_parent->hasBubble()) { - const auto maxWithCaption = qMin( - st::msgMaxWidth, - _parent->textualMaxWidth()); - newWidth = qMin(qMax(newWidth, maxWithCaption), thumbMaxWidth); - } - if (newWidth >= maxWidth()) { - newHeight = qMin(newHeight, minHeight()); - } - return { newWidth, newHeight }; -} - -int ExtendedPreview::minWidthForButton() const { - return (st::msgBotKbButton.margin + st::msgBotKbButton.padding) * 2 - + _buttonText.maxWidth(); -} - -void ExtendedPreview::draw(Painter &p, const PaintContext &context) const { - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; - - auto paintx = 0, painty = 0, paintw = width(), painth = height(); - auto bubble = _parent->hasBubble(); - auto rthumb = style::rtlrect(paintx, painty, paintw, painth, width()); - const auto inWebPage = (_parent->media() != this); - const auto rounding = inWebPage - ? std::optional() - : adjustedBubbleRounding(); - if (!bubble) { - Assert(rounding.has_value()); - fillImageShadow(p, rthumb, *rounding, context); - } - validateImageCache(rthumb.size(), rounding); - p.drawImage(rthumb.topLeft(), _spoiler.background); - fillImageSpoiler(p, &_spoiler, rthumb, context); - paintButton(p, rthumb, context); - if (context.selected()) { - fillImageOverlay(p, rthumb, rounding, context); - } - - // date - if (!inWebPage) { - auto fullRight = paintx + paintw; - auto fullBottom = painty + painth; - if (needInfoDisplay()) { - _parent->drawInfo( - p, - context, - fullRight, - fullBottom, - 2 * paintx + paintw, - InfoDisplayType::Image); - } - if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) { - auto fastShareLeft = _parent->hasRightLayout() - ? (paintx - size->width() - st::historyFastShareLeft) - : (fullRight + st::historyFastShareLeft); - auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height()); - _parent->drawRightAction(p, context, fastShareLeft, fastShareTop, 2 * paintx + paintw); - } - } -} - -void ExtendedPreview::validateImageCache( - QSize outer, - std::optional rounding) const { - const auto ratio = style::DevicePixelRatio(); - if (_spoiler.background.size() == (outer * ratio) - && _spoiler.backgroundRounding == rounding) { - return; - } - _spoiler.background = Images::Round( - prepareImageCache(outer), - MediaRoundingMask(rounding)); - _spoiler.backgroundRounding = rounding; -} - -QImage ExtendedPreview::prepareImageCache(QSize outer) const { - ensureThumbnailRead(); - return PrepareWithBlurredBackground(outer, {}, {}, _inlineThumbnail); -} - -void ExtendedPreview::paintButton( - Painter &p, - QRect outer, - const PaintContext &context) const { - const auto st = context.st; - const auto &padding = st::extendedPreviewButtonPadding; - const auto margin = st::extendedPreviewButtonMargin; - const auto width = std::min( - _buttonText.maxWidth() + padding.left() + padding.right(), - outer.width() - 2 * margin); - const auto height = padding.top() - + st::semiboldFont->height - + padding.bottom(); - const auto overlay = st->msgDateImgBg()->c; - const auto ratio = style::DevicePixelRatio(); - const auto size = QSize(width, height); - if (_buttonBackground.size() != size * ratio - || _buttonBackgroundOverlay != overlay) { - auto &background = _spoiler.background; - if (background.width() < width * ratio - || background.height() < height * ratio) { - return; - } - _buttonBackground = background.copy(QRect( - (background.width() - width * ratio) / 2, - (background.height() - height * ratio) / 2, - width * ratio, - height * ratio)); - _buttonBackground.setDevicePixelRatio(ratio); - auto p = QPainter(&_buttonBackground); - p.fillRect(0, 0, width, height, overlay); - p.end(); - _buttonBackground = Images::Round( - std::move(_buttonBackground), - Images::CornersMask(height / 2)); - } - const auto left = outer.x() + (outer.width() - width) / 2; - const auto top = outer.y() + (outer.height() - height) / 2; - p.drawImage(left, top, _buttonBackground); - p.setPen(st->msgDateImgFg()->c); - _buttonText.drawLeftElided( - p, - left + padding.left(), - top + padding.top(), - width - padding.left() - padding.right(), - outer.width()); -} - -TextState ExtendedPreview::textState(QPoint point, StateRequest request) const { - auto result = TextState(_parent); - - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { - return result; - } - auto paintx = 0, painty = 0, paintw = width(), painth = height(); - auto bubble = _parent->hasBubble(); - if (QRect(paintx, painty, paintw, painth).contains(point)) { - result.link = _spoiler.link; - } - if (!bubble && _parent->media() == this) { - auto fullRight = paintx + paintw; - auto fullBottom = painty + painth; - const auto bottomInfoResult = _parent->bottomInfoTextState( - fullRight, - fullBottom, - point, - InfoDisplayType::Image); - if (bottomInfoResult.link - || bottomInfoResult.cursor != CursorState::None - || bottomInfoResult.customTooltip) { - return bottomInfoResult; - } - if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) { - auto fastShareLeft = _parent->hasRightLayout() - ? (paintx - size->width() - st::historyFastShareLeft) - : (fullRight + st::historyFastShareLeft); - auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height()); - if (QRect(fastShareLeft, fastShareTop, size->width(), size->height()).contains(point)) { - result.link = _parent->rightActionLink(point - - QPoint(fastShareLeft, fastShareTop)); - } - } - } - return result; -} - -bool ExtendedPreview::toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const { - return p == _spoiler.link; -} - -bool ExtendedPreview::dragItemByHandler(const ClickHandlerPtr &p) const { - return p == _spoiler.link; -} - -bool ExtendedPreview::needInfoDisplay() const { - return _parent->data()->isSending() - || _parent->data()->hasFailed() - || _parent->isUnderCursor() - || (_parent->delegate()->elementContext() == Context::ChatPreview) - || _parent->isLastAndSelfMessage(); -} - -bool ExtendedPreview::needsBubble() const { - const auto item = _parent->data(); - return !item->isService() - && (item->repliesAreComments() - || item->externalReply() - || item->viaBot() - || !item->emptyText() - || _parent->displayReply() - || _parent->displayForwardedFrom() - || _parent->displayFromName() - || _parent->displayedTopicButton()); -} - -QPoint ExtendedPreview::resolveCustomInfoRightBottom() const { - const auto skipx = (st::msgDateImgDelta + st::msgDateImgPadding.x()); - const auto skipy = (st::msgDateImgDelta + st::msgDateImgPadding.y()); - return QPoint(width() - skipx, height() - skipy); -} - -} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h deleted file mode 100644 index da2cd5d14..000000000 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h +++ /dev/null @@ -1,87 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#pragma once - -#include "history/view/media/history_view_media.h" -#include "history/view/media/history_view_media_spoiler.h" - -enum class ImageRoundRadius; - -namespace Ui { -class SpoilerAnimation; -} // namespace Ui - -namespace Data { -struct Invoice; -} // namespace Data - -namespace HistoryView { - -class Element; - -class ExtendedPreview final : public Media { -public: - ExtendedPreview( - not_null parent, - not_null invoice); - ~ExtendedPreview(); - - bool hideMessageText() const override { - return false; - } - - void draw(Painter &p, const PaintContext &context) const override; - TextState textState(QPoint point, StateRequest request) const override; - - [[nodiscard]] bool toggleSelectionByHandlerClick( - const ClickHandlerPtr &p) const override; - [[nodiscard]] bool dragItemByHandler( - const ClickHandlerPtr &p) const override; - - bool needsBubble() const override; - bool customInfoLayout() const override { - return true; - } - QPoint resolveCustomInfoRightBottom() const override; - bool skipBubbleTail() const override { - return isRoundedInBubbleBottom(); - } - - bool hasHeavyPart() const override; - void unloadHeavyPart() override; - bool enforceBubbleWidth() const override; - -private: - int minWidthForButton() const; - void resolveButtonText(); - void ensureThumbnailRead() const; - - QSize countOptimalSize() override; - QSize countCurrentSize(int newWidth) override; - - bool needInfoDisplay() const; - void validateImageCache( - QSize outer, - std::optional rounding) const; - [[nodiscard]] QImage prepareImageCache(QSize outer) const; - void paintButton( - Painter &p, - QRect outer, - const PaintContext &context) const; - - const not_null _invoice; - mutable MediaSpoiler _spoiler; - mutable QImage _inlineThumbnail; - mutable QImage _buttonBackground; - mutable QColor _buttonBackgroundOverlay; - mutable Ui::Text::String _buttonText; - mutable bool _imageCacheInvalid = false; - -}; - -} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 2205625d8..01a41bdaa 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -219,10 +219,14 @@ QSize Photo::countCurrentSize(int newWidth) { : st::minPhotoSize), thumbMaxWidth); const auto dimensions = photoSize(); - auto pix = CountPhotoMediaSize( - CountDesiredMediaSize(dimensions), - newWidth, - maxWidth()); + auto pix = _data->extendedMediaVideoDuration() + ? CountMediaSize( + CountDesiredMediaSize(dimensions), + newWidth) + : CountPhotoMediaSize( + CountDesiredMediaSize(dimensions), + newWidth, + maxWidth()); newWidth = qMax(pix.width(), minWidth); auto newHeight = qMax(pix.height(), st::minPhotoSize); if (_parent->hasBubble()) { diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 2d2da578f..33f08c5a1 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -1362,7 +1362,7 @@ void OverlayWidget::updateControls() { if (_message) { return ItemDateTime(_message); } else if (_photo) { - return base::unixtime::parse(_photo->date); + return base::unixtime::parse(_photo->date()); } else if (_document) { return base::unixtime::parse(_document->date); } @@ -2436,7 +2436,7 @@ void OverlayWidget::saveAs() { u".mp4"_q, QString(), false, - _photo->date), + _photo->date()), crl::guard(_window, [=](const QString &result) { QFile f(result); if (!result.isEmpty() @@ -2467,7 +2467,7 @@ void OverlayWidget::saveAs() { u".jpg"_q, QString(), false, - _photo->date), + _photo->date()), crl::guard(_window, [=](const QString &result) { if (!result.isEmpty() && _photo == photo) { media->saveToFile(result); diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 5d35cc578..9aa1c847d 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -44,6 +44,7 @@ inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector s inputMediaDice#e66fbf7b emoticon:string = InputMedia; inputMediaStory#89fdd778 peer:InputPeer id:int = InputMedia; inputMediaWebPage#c21b8849 flags:# force_large_media:flags.0?true force_small_media:flags.1?true optional:flags.2?true url:string = InputMedia; +inputMediaPaidMedia#aa661fc3 stars_amount:long extended_media:Vector = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatUploadedPhoto#bdcdaec0 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.3?VideoSize = InputChatPhoto; @@ -133,6 +134,7 @@ messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia; messageMediaStory#68cb6283 flags:# via_mention:flags.1?true peer:Peer id:int story:flags.0?StoryItem = MessageMedia; messageMediaGiveaway#daad85b0 flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.2?true channels:Vector countries_iso2:flags.1?Vector prize_description:flags.3?string quantity:int months:int until_date:int = MessageMedia; messageMediaGiveawayResults#c6991068 flags:# only_new_subscribers:flags.0?true refunded:flags.2?true channel_id:long additional_peers_count:flags.3?int launch_msg_id:int winners_count:int unclaimed_count:int winners:Vector months:int prize_description:flags.1?string until_date:int = MessageMedia; +messageMediaPaidMedia#a8852491 stars_amount:long extended_media:Vector = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#bd47cbad title:string users:Vector = MessageAction; @@ -384,7 +386,7 @@ updateUserEmojiStatus#28373599 user_id:long emoji_status:EmojiStatus = Update; updateRecentEmojiStatuses#30f443db = Update; updateRecentReactions#6f7863f4 = Update; updateMoveStickerSetToTop#86fccf85 flags:# masks:flags.0?true emojis:flags.1?true stickerset:long = Update; -updateMessageExtendedMedia#5a73a98c peer:Peer msg_id:int extended_media:MessageExtendedMedia = Update; +updateMessageExtendedMedia#d5a41724 peer:Peer msg_id:int extended_media:Vector = Update; updateChannelPinnedTopic#192efbe3 flags:# pinned:flags.0?true channel_id:long topic_id:int = Update; updateChannelPinnedTopics#fe198602 flags:# channel_id:long order:flags.0?Vector = Update; updateUser#20529438 user_id:long = Update; @@ -1806,7 +1808,7 @@ starsTransactionPeer#d80da15d peer:Peer = StarsTransactionPeer; starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; -starsTransaction#aa00c898 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string = StarsTransaction; +starsTransaction#2db5418f flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector = StarsTransaction; payments.starsStatus#8cf4ee60 flags:# balance:long history:Vector next_offset:flags.0?string chats:Vector users:Vector = payments.StarsStatus; @@ -2488,4 +2490,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo; -// LAYER 182 +// LAYER 183 diff --git a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp index 9724bdec2..5594e6c4c 100644 --- a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp +++ b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp @@ -968,7 +968,7 @@ CopyRestrictionType ShortcutMessages::listCopyMediaRestrictionType( not_null item) { if (const auto media = item->media()) { if (const auto invoice = media->invoice()) { - if (invoice->extendedMedia) { + if (!invoice->extendedMedia.empty()) { return CopyMediaRestrictionTypeFor(_history->peer, item); } } diff --git a/Telegram/SourceFiles/ui/controls/userpic_button.cpp b/Telegram/SourceFiles/ui/controls/userpic_button.cpp index 1b86567b3..0b26e1810 100644 --- a/Telegram/SourceFiles/ui/controls/userpic_button.cpp +++ b/Telegram/SourceFiles/ui/controls/userpic_button.cpp @@ -416,7 +416,7 @@ void UserpicButton::openPeerPhoto() { return; } const auto photo = _peer->owner().photo(id); - if (photo->date && _controller) { + if (photo->date() && _controller) { _controller->openPhoto(photo, _peer); } } @@ -744,7 +744,7 @@ void UserpicButton::updateVideo() { return; } const auto photo = _peer->owner().photo(id); - if (!photo->date || !photo->videoCanBePlayed()) { + if (!photo->date() || !photo->videoCanBePlayed()) { clearStreaming(); return; } else if (_streamed && _streamedPhoto == photo) { From a9bd7803e6a25138c5b7ee265ce881bd8bd6ce27 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 18 Jun 2024 18:55:07 +0400 Subject: [PATCH 076/142] Edit price on send, send single paid media. --- Telegram/Resources/langs/lang.strings | 9 + Telegram/SourceFiles/api/api_common.h | 1 + Telegram/SourceFiles/api/api_views.cpp | 29 ++- Telegram/SourceFiles/api/api_views.h | 3 +- Telegram/SourceFiles/apiwrap.cpp | 6 +- .../SourceFiles/boxes/edit_caption_box.cpp | 1 + Telegram/SourceFiles/boxes/send_files_box.cpp | 168 +++++++++++++++++- Telegram/SourceFiles/boxes/send_files_box.h | 10 +- .../chat_helpers/chat_helpers.style | 14 ++ Telegram/SourceFiles/data/data_cloud_file.cpp | 6 + .../SourceFiles/data/data_media_types.cpp | 4 +- Telegram/SourceFiles/data/data_media_types.h | 1 + .../view/media/history_view_media_common.cpp | 41 +++++ .../view/media/history_view_media_common.h | 2 + .../history/view/media/history_view_photo.cpp | 61 ++++++- .../history/view/media/history_view_photo.h | 3 + .../SourceFiles/media/view/media_view.style | 1 + Telegram/SourceFiles/menu/menu_send.cpp | 8 + Telegram/SourceFiles/menu/menu_send.h | 2 + .../payments/payments_non_panel_process.cpp | 3 - .../payments/payments_non_panel_process.h | 2 + .../attach_abstract_single_media_preview.cpp | 8 +- .../attach_abstract_single_media_preview.h | 4 +- .../ui/chat/attach/attach_album_preview.cpp | 6 +- .../ui/chat/attach/attach_album_preview.h | 4 +- .../attach_item_single_media_preview.cpp | 2 +- .../ui/chat/attach/attach_prepare.cpp | 4 + .../ui/chat/attach/attach_prepare.h | 3 + .../attach/attach_single_media_preview.cpp | 9 +- .../chat/attach/attach_single_media_preview.h | 4 +- 30 files changed, 382 insertions(+), 37 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0043c59b3..f4d2c145d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3311,6 +3311,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_spoiler_effect" = "Hide with Spoiler"; "lng_context_disable_spoiler" = "Remove Spoiler"; +"lng_context_make_paid" = "Make This Content Paid"; +"lng_context_change_price" = "Change Price"; "lng_factcheck_title" = "Fact Check"; "lng_factcheck_placeholder" = "Add Facts or Context"; @@ -3322,6 +3324,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_factcheck_bottom" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country ({country}) responsible for combatting misinformation."; "lng_factcheck_links" = "Only **t.me/** links are allowed."; +"lng_paid_title" = "Paid Content"; +"lng_paid_enter_cost" = "Enter Unlock Cost"; +"lng_paid_cost_placeholder" = "Stars to Unlock"; +"lng_paid_about" = "Users will have to transfer this amount of Stars to your channel in order to view this media. {link}"; +"lng_paid_about_link" = "More about stars >"; +"lng_paid_price" = "Unlock for {price}"; + "lng_translate_show_original" = "Show Original"; "lng_translate_bar_to" = "Translate to {name}"; "lng_translate_bar_to_other" = "Translate to {name}"; diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index efd92a9bc..cd8aa54e2 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -20,6 +20,7 @@ namespace Api { inline constexpr auto kScheduledUntilOnlineTimestamp = TimeId(0x7FFFFFFE); struct SendOptions { + uint64 price = 0; PeerData *sendAs = nullptr; TimeId scheduled = 0; BusinessShortcutId shortcutId = 0; diff --git a/Telegram/SourceFiles/api/api_views.cpp b/Telegram/SourceFiles/api/api_views.cpp index 6c8ec8df7..e80f56e21 100644 --- a/Telegram/SourceFiles/api/api_views.cpp +++ b/Telegram/SourceFiles/api/api_views.cpp @@ -55,7 +55,9 @@ void ViewsManager::removeIncremented(not_null peer) { _incremented.remove(peer); } -void ViewsManager::pollExtendedMedia(not_null item) { +void ViewsManager::pollExtendedMedia( + not_null item, + bool force) { if (!item->isRegular()) { return; } @@ -63,14 +65,20 @@ void ViewsManager::pollExtendedMedia(not_null item) { const auto peer = item->history()->peer; auto &request = _pollRequests[peer]; if (request.ids.contains(id) || request.sent.contains(id)) { - return; + if (!force || request.forced) { + return; + } } request.ids.emplace(id); - if (!request.id && !request.when) { - request.when = crl::now() + kPollExtendedMediaPeriod; + if (force) { + request.forced = true; } - if (!_pollTimer.isActive()) { - _pollTimer.callOnce(kPollExtendedMediaPeriod); + const auto delay = force ? 1 : kPollExtendedMediaPeriod; + if (!request.id && (!request.when || force)) { + request.when = crl::now() + delay; + } + if (!_pollTimer.isActive() || force) { + _pollTimer.callOnce(delay); } } @@ -160,9 +168,12 @@ void ViewsManager::sendPollRequests( if (i->second.ids.empty()) { i = _pollRequests.erase(i); } else { - i->second.when = now + kPollExtendedMediaPeriod; - if (!_pollTimer.isActive()) { - _pollTimer.callOnce(kPollExtendedMediaPeriod); + const auto delay = i->second.forced + ? 1 + : kPollExtendedMediaPeriod; + i->second.when = now + delay; + if (!_pollTimer.isActive() || i->second.forced) { + _pollTimer.callOnce(delay); } ++i; } diff --git a/Telegram/SourceFiles/api/api_views.h b/Telegram/SourceFiles/api/api_views.h index 759d3c6a8..f8d19d15b 100644 --- a/Telegram/SourceFiles/api/api_views.h +++ b/Telegram/SourceFiles/api/api_views.h @@ -26,7 +26,7 @@ public: void scheduleIncrement(not_null item); void removeIncremented(not_null peer); - void pollExtendedMedia(not_null item); + void pollExtendedMedia(not_null item, bool force = false); private: struct PollExtendedMediaRequest { @@ -34,6 +34,7 @@ private: mtpRequestId id = 0; base::flat_set ids; base::flat_set sent; + bool forced = false; }; void viewsIncrement(); diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 62f2cfbd4..424c8ccc1 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -4188,7 +4188,11 @@ void ApiWrap::sendMediaWithRandomId( MTP_flags(flags), peer->input, Data::Histories::ReplyToPlaceholder(), - media, + (options.price + ? MTPInputMedia(MTP_inputMediaPaidMedia( + MTP_long(options.price), + MTP_vector(1, media))) + : media), MTP_string(caption.text), MTP_long(randomId), MTPReplyMarkup(), diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index d46121c2f..bd089a9b5 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -463,6 +463,7 @@ void EditCaptionBox::rebuildPreview() { st::defaultComposeControls, gifPaused, file, + [] { return true; }, Ui::AttachControls::Type::EditOnly); _isPhoto = (media && media->isPhoto()); const auto withCheckbox = _isPhoto && CanBeCompressed(_albumType); diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index fbaaf561a..f80279345 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "storage/localstorage.h" #include "storage/storage_media_prepare.h" +#include "iv/iv_instance.h" #include "mainwidget.h" #include "main/main_session.h" #include "main/main_session_settings.h" @@ -36,9 +37,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/attach/attach_single_file_preview.h" #include "ui/chat/attach/attach_single_media_preview.h" #include "ui/grouped_layout.h" +#include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/controls/emoji_button.h" #include "ui/painter.h" +#include "ui/vertical_list.h" #include "lottie/lottie_single_player.h" #include "data/data_document.h" #include "data/data_user.h" @@ -58,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { constexpr auto kMaxMessageLength = 4096; +constexpr auto kMaxPrice = 1000ULL; using Ui::SendFilesWay; @@ -103,6 +107,74 @@ rpl::producer FieldPlaceholder( : tr::lng_photos_comment(); } +void EditPriceBox( + not_null box, + not_null session, + uint64 price, + Fn apply) { + const auto owner = &session->data(); + box->setTitle(tr::lng_paid_title()); + AddSubsectionTitle( + box->verticalLayout(), + tr::lng_paid_enter_cost(), + (st::boxRowPadding - QMargins( + st::defaultSubsectionTitlePadding.left(), + 0, + st::defaultSubsectionTitlePadding.right(), + 0))); + const auto field = box->addRow(object_ptr( + box, + st::editTagField, + tr::lng_paid_cost_placeholder(), + price ? QString::number(price) : QString())); + field->selectAll(); + field->setMaxLength(QString::number(kMaxPrice).size()); + box->setFocusCallback([=] { + field->setFocusFast(); + }); + const auto about = box->addRow( + object_ptr( + box, + tr::lng_paid_about( + lt_link, + tr::lng_paid_about_link() | Ui::Text::ToLink(), + Ui::Text::WithEntities), + st::paidAmountAbout), + st::boxRowPadding + QMargins(0, st::sendMediaRowSkip, 0, 0)); + about->setClickHandlerFilter([=](const auto &...) { + Core::App().iv().openWithIvPreferred( + session, + u"https://telegram.org/blog/telegram-stars"_q); + return false; + }); + + field->paintRequest() | rpl::start_with_next([=](QRect clip) { + auto p = QPainter(field); + st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width()); + }, field->lifetime()); + + const auto save = [=] { + const auto now = field->getLastText().toULongLong(); + if (now > kMaxPrice) { + field->showError(); + return; + } + const auto weak = Ui::MakeWeak(box); + apply(now); + if (const auto strong = weak.data()) { + strong->closeBox(); + } + }; + + field->submits( + ) | rpl::start_with_next(save, field->lifetime()); + + box->addButton(tr::lng_settings_save(), save); + box->addButton(tr::lng_cancel(), [=] { + box->closeBox(); + }); +} + } // namespace SendFilesLimits DefaultLimitsForPeer(not_null peer) { @@ -153,7 +225,8 @@ SendFilesBox::Block::Block( int from, int till, Fn gifPaused, - SendFilesWay way) + SendFilesWay way, + Fn canToggleSpoiler) : _items(items) , _from(from) , _till(till) { @@ -170,14 +243,16 @@ SendFilesBox::Block::Block( parent.get(), st, my, - way); + way, + std::move(canToggleSpoiler)); _preview.reset(preview); } else { const auto media = Ui::SingleMediaPreview::Create( parent, st, gifPaused, - first); + first, + std::move(canToggleSpoiler)); if (media) { _isSingleMedia = true; _preview.reset(media); @@ -385,6 +460,9 @@ Fn SendFilesBox::prepareSendMenuDetails( : _invertCaption ? SendMenu::CaptionState::Above : SendMenu::CaptionState::Below; + result.price = canChangePrice() + ? _price.current() + : std::optional(); return result; }); } @@ -398,6 +476,7 @@ auto SendFilesBox::prepareSendMenuCallback() case Type::CaptionUp: _invertCaption = true; break; case Type::SpoilerOn: toggleSpoilers(true); break; case Type::SpoilerOff: toggleSpoilers(false); break; + case Type::ChangePrice: changePrice(); break; default: SendMenu::DefaultCallback( _show, @@ -588,14 +667,22 @@ void SendFilesBox::refreshButtons() { addMenuButton(); } -bool SendFilesBox::hasSendMenu(const SendMenu::Details &details) const { +bool SendFilesBox::hasSendMenu(const MenuDetails &details) const { return (details.type != SendMenu::Type::Disabled) || (details.spoiler != SendMenu::SpoilerState::None) || (details.caption != SendMenu::CaptionState::None); } bool SendFilesBox::hasSpoilerMenu() const { - return _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos()); + return !hasPrice() + && _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos()); +} + +bool SendFilesBox::canChangePrice() const { + const auto way = _sendWay.current(); + return _list.canChangePrice( + way.groupFiles() && way.sendImagesAsPhotos(), + way.sendImagesAsPhotos()); } void SendFilesBox::applyBlockChanges() { @@ -618,6 +705,71 @@ void SendFilesBox::toggleSpoilers(bool enabled) { } } +void SendFilesBox::changePrice() { + const auto weak = Ui::MakeWeak(this); + const auto session = &_show->session(); + const auto now = _price.current(); + _show->show(Box(EditPriceBox, session, now, [=](uint64 price) { + if (weak && price != now) { + _price = price; + refreshPriceTag(); + } + })); +} + +bool SendFilesBox::hasPrice() const { + return canChangePrice() && _price.current() > 0; +} + +void SendFilesBox::refreshPriceTag() { + const auto resetSpoilers = hasPrice() || _priceTag; + if (resetSpoilers) { + for (auto &file : _list.files) { + file.spoiler = false; + } + for (auto &block : _blocks) { + block.toggleSpoilers(hasPrice()); + } + } + if (!hasPrice()) { + _priceTag = nullptr; + } else if (!_priceTag) { + _priceTag = std::make_unique(_inner.data()); + const auto raw = _priceTag.get(); + + raw->show(); + raw->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(raw); + auto hq = PainterHighQualityEnabler(p); + p.setBrush(st::toastBg); + p.setPen(Qt::NoPen); + const auto radius = std::min(raw->width(), raw->height()) / 2.; + p.drawRoundedRect(raw->rect(), radius, radius); + }, raw->lifetime()); + + auto price = _price.value() | rpl::map([=](uint64 amount) { + return QChar(0x2B50) + Lang::FormatCountDecimal(amount); + }); + const auto label = Ui::CreateChild( + raw, + tr::lng_paid_price(lt_price, std::move(price)), + st::paidTagLabel); + label->sizeValue() | rpl::start_with_next([=](QSize size) { + const auto inner = QRect(QPoint(), size); + const auto rect = inner.marginsAdded(st::paidTagPadding); + raw->resize(rect.size()); + label->move(-rect.topLeft()); + }, label->lifetime()); + _inner->sizeValue() | rpl::start_with_next([=](QSize size) { + raw->move( + (size.width() - raw->width()) / 2, + (size.height() - raw->height()) / 2); + }, raw->lifetime()); + } else { + _priceTag->raise(); + } +} + void SendFilesBox::addMenuButton() { const auto details = _sendMenuDetails(); if (!hasSendMenu(details)) { @@ -766,7 +918,8 @@ void SendFilesBox::pushBlock(int from, int till) { from, till, gifPaused, - _sendWay.current()); + _sendWay.current(), + [=] { return !hasPrice(); }); auto &block = _blocks.back(); const auto widget = _inner->add( block.takeWidget(), @@ -893,6 +1046,7 @@ void SendFilesBox::pushBlock(int from, int till) { void SendFilesBox::refreshControls(bool initial) { refreshButtons(); + refreshPriceTag(); refreshTitleText(); updateSendWayControls(); updateCaptionPlaceholder(); @@ -1447,6 +1601,7 @@ void SendFilesBox::send( auto child = _sendMenuDetails(); child.spoiler = SendMenu::SpoilerState::None; child.caption = SendMenu::CaptionState::None; + child.price = std::nullopt; return SendMenu::DefaultCallback(_show, sendCallback())( { .type = SendMenu::ActionType::Schedule }, child); @@ -1475,6 +1630,7 @@ void SendFilesBox::send( ? _caption->getTextWithAppliedMarkdown() : TextWithTags(); options.invertCaption = _invertCaption; + options.price = hasPrice() ? _price.current() : 0; if (!validateLength(caption.text)) { return; } diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index 1e95a1aa8..f16dffb8f 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -149,7 +149,8 @@ private: int from, int till, Fn gifPaused, - Ui::SendFilesWay way); + Ui::SendFilesWay way, + Fn canToggleSpoiler); Block(Block &&other) = default; Block &operator=(Block &&other) = default; @@ -190,6 +191,11 @@ private: void addMenuButton(); void applyBlockChanges(); void toggleSpoilers(bool enabled); + void changePrice(); + + [[nodiscard]] bool canChangePrice() const; + [[nodiscard]] bool hasPrice() const; + void refreshPriceTag(); bool validateLength(const QString &text) const; void refreshButtons(); @@ -251,6 +257,8 @@ private: SendFilesCheck _check; SendFilesConfirmed _confirmedCallback; Fn _cancelledCallback; + rpl::variable _price = 0; + std::unique_ptr _priceTag; bool _confirmed = false; bool _invertCaption = false; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 6b69aa136..b268377f9 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -71,6 +71,7 @@ ComposeIcons { menuSpoilerOff: icon; menuBelow: icon; menuAbove: icon; + menuPrice: icon; stripBubble: icon; stripExpandPanel: icon; @@ -610,6 +611,7 @@ defaultComposeIcons: ComposeIcons { menuSpoilerOff: menuIconSpoilerOff; menuBelow: menuIconBelow; menuAbove: menuIconAbove; + menuPrice: menuIconEarn; stripBubble: icon{ { "chat/reactions_bubble_shadow", windowShadowFg }, @@ -1406,3 +1408,15 @@ editTagField: InputField(defaultInputField) { editTagLimit: FlatLabel(defaultFlatLabel) { textFg: windowSubTextFg; } + +paidStarIcon: icon {{ "settings/premium/star", creditsBg1 }}; +paidStarIconTop: 7px; +paidAmountAbout: FlatLabel(defaultFlatLabel) { + minWidth: 256px; + textFg: windowSubTextFg; +} +paidTagLabel: FlatLabel(defaultFlatLabel) { + textFg: radialFg; + style: semiboldTextStyle; +} +paidTagPadding: margins(16px, 6px, 16px, 6px); diff --git a/Telegram/SourceFiles/data/data_cloud_file.cpp b/Telegram/SourceFiles/data/data_cloud_file.cpp index 526dd8904..7b3e73828 100644 --- a/Telegram/SourceFiles/data/data_cloud_file.cpp +++ b/Telegram/SourceFiles/data/data_cloud_file.cpp @@ -170,6 +170,12 @@ void UpdateCloudFile( if (data.progressivePartSize && !file.location.valid()) { file.progressivePartSize = data.progressivePartSize; } + if (data.location.width() + && data.location.height() + && !file.location.valid() + && !file.location.width()) { + file.location = data.location; + } return; } diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index e076438d7..1a8b052db 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -323,7 +323,7 @@ bool UpdateExtendedMedia( auto changed = false; const auto count = int(media.size()); for (auto i = 0; i != count; ++i) { - if (i < invoice.extendedMedia.size()) { + if (i <= invoice.extendedMedia.size()) { invoice.extendedMedia.emplace_back(); changed = true; } @@ -388,6 +388,7 @@ Invoice ComputeInvoiceData( auto result = Invoice{ .amount = data.vstars_amount().v, .currency = Ui::kCreditsCurrency, + .isPaidMedia = true, }; UpdateExtendedMedia(result, item, data.vextended_media().v); return result; @@ -1908,6 +1909,7 @@ MediaInvoice::MediaInvoice( .title = data.title, .description = data.description, .photo = data.photo, + .isPaidMedia = data.isPaidMedia, .isTest = data.isTest, } { _invoice.extendedMedia.reserve(data.extendedMedia.size()); diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 3e26877d4..62d03c53d 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -94,6 +94,7 @@ struct Invoice { TextWithEntities description; std::vector> extendedMedia; PhotoData *photo = nullptr; + bool isPaidMedia = false; bool isTest = false; }; [[nodiscard]] bool HasExtendedMedia(const Invoice &invoice); diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp index 273262e8d..e5c01ba70 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp @@ -7,9 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/media/history_view_media_common.h" +#include "api/api_views.h" +#include "apiwrap.h" #include "ui/text/format_values.h" #include "ui/painter.h" +#include "core/click_handler_types.h" #include "data/data_document.h" +#include "data/data_session.h" #include "data/data_wall_paper.h" #include "data/data_media_types.h" #include "history/view/history_view_element.h" @@ -19,7 +23,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_document.h" #include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_theme_document.h" +#include "history/history_item.h" +#include "history/history.h" +#include "main/main_session.h" +#include "mainwindow.h" #include "media/streaming/media_streaming_utility.h" +#include "payments/payments_checkout_process.h" +#include "payments/payments_non_panel_process.h" +#include "window/window_session_controller.h" #include "styles/style_chat.h" namespace HistoryView { @@ -180,4 +191,34 @@ QSize CountPhotoMediaSize( media.scaled(media.width(), newWidth, Qt::KeepAspectRatio)); } +ClickHandlerPtr MakePaidMediaLink(not_null item) { + return std::make_shared([=](ClickContext context) { + const auto my = context.other.value(); + const auto controller = my.sessionWindow.get(); + const auto itemId = item->fullId(); + const auto session = &item->history()->session(); + using Result = Payments::CheckoutResult; + const auto done = crl::guard(session, [=](Result result) { + if (result != Result::Paid) { + return; + } else if (const auto item = session->data().message(itemId)) { + session->api().views().pollExtendedMedia(item, true); + } + }); + Payments::CheckoutProcess::Start( + item, + Payments::Mode::Payment, + (controller + ? crl::guard( + controller, + [=](auto) { controller->widget()->activate(); }) + : Fn()), + ((controller && Payments::IsCreditsInvoice(item)) + ? Payments::ProcessNonPanelPaymentFormFactory( + controller, + done) + : nullptr)); + }); +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.h b/Telegram/SourceFiles/history/view/media/history_view_media_common.h index 2a46f5b3e..a37d0ab62 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_common.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.h @@ -75,4 +75,6 @@ void PaintInterpolatedIcon( int newWidth, int maxWidth); +[[nodiscard]] ClickHandlerPtr MakePaidMediaLink(not_null item); + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 01a41bdaa..7650fcd9a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_cursor_state.h" #include "history/view/media/history_view_media_common.h" #include "history/view/media/history_view_media_spoiler.h" +#include "lang/lang_keys.h" #include "media/streaming/media_streaming_instance.h" #include "media/streaming/media_streaming_player.h" #include "media/streaming/media_streaming_document.h" @@ -38,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_web_page.h" #include "core/application.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" namespace HistoryView { namespace { @@ -140,7 +142,8 @@ void Photo::dataMediaCreated() const { if (_data->inlineThumbnailBytes().isEmpty() && !_dataMedia->image(PhotoSize::Large) - && !_dataMedia->image(PhotoSize::Thumbnail)) { + && !_dataMedia->image(PhotoSize::Thumbnail) + && !_data->extendedMediaPreview()) { _dataMedia->wanted(PhotoSize::Small, _realParent->fullId()); } history()->owner().registerHeavyViewPart(_parent); @@ -277,8 +280,9 @@ void Photo::draw(Painter &p, const PaintContext &context) const { _dataMedia->automaticLoad(_realParent->fullId(), _parent->data()); const auto st = context.st; const auto sti = context.imageStyle(); - auto loaded = _dataMedia->loaded(); - auto displayLoading = _data->displayLoading(); + const auto preview = _data->extendedMediaPreview(); + auto loaded = preview || _dataMedia->loaded(); + auto displayLoading = !preview && _data->displayLoading(); auto inWebPage = (_parent->media() != this); auto paintx = 0, painty = 0, paintw = width(), painth = height(); @@ -365,6 +369,8 @@ void Photo::draw(Painter &p, const PaintContext &context) const { QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); _animation->radial.draw(p, rinner, st::msgFileRadialLine, sti->historyFileThumbRadialFg); } + } else if (preview) { + paintPriceTag(p, rthumb); } if (showEnlarge) { auto hq = PainterHighQualityEnabler(p); @@ -397,6 +403,43 @@ void Photo::draw(Painter &p, const PaintContext &context) const { } } +void Photo::paintPriceTag(Painter &p, QRect rthumb) const { + const auto media = parent()->data()->media(); + const auto invoice = media ? media->invoice() : nullptr; + const auto price = invoice->isPaidMedia ? invoice->amount : 0; + if (!price) { + return; + } + + auto text = Ui::Text::String(); + text.setText( + st::semiboldTextStyle, + tr::lng_paid_price( + tr::now, + lt_price, + QChar(0x2B50) + Lang::FormatCountDecimal(invoice->amount))); + const auto width = text.maxWidth(); + const auto inner = QRect(0, 0, width, text.minHeight()); + const auto outer = inner.marginsAdded(st::paidTagPadding); + const auto size = outer.size(); + + auto hq = PainterHighQualityEnabler(p); + p.setBrush(st::toastBg); + p.setPen(Qt::NoPen); + + const auto radius = std::min(size.width(), size.height()) / 2.; + const auto rect = QRect( + rthumb.x() + (rthumb.width() - size.width()) / 2, + rthumb.y() + (rthumb.height() - size.height()) / 2, + size.width(), + size.height()); + + p.drawRoundedRect(rect, radius, radius); + p.setPen(st::toastFg); + + text.draw(p, rect.x() - outer.x(), rect.y() - outer.y(), width); +} + void Photo::validateUserpicImageCache(QSize size, bool forum) const { const auto forumValue = forum ? 1 : 0; const auto large = _dataMedia->image(PhotoSize::Large); @@ -604,6 +647,14 @@ QRect Photo::enlargeRect() const { }; } +ClickHandlerPtr Photo::ensureExtendedMediaLink() const { + const auto item = parent()->data(); + if (!_extendedMediaLink && item->isRegular()) { + _extendedMediaLink = MakePaidMediaLink(item); + } + return _extendedMediaLink; +} + TextState Photo::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); @@ -617,7 +668,9 @@ TextState Photo::textState(QPoint point, StateRequest request) const { if (QRect(paintx, painty, paintw, painth).contains(point)) { ensureDataMediaCreated(); - result.link = (_spoiler && !_spoiler->revealed) + result.link = _data->extendedMediaPreview() + ? ensureExtendedMediaLink() + : (_spoiler && !_spoiler->revealed) ? _spoiler->link : _data->uploading() ? _cancell diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index 44fbed00c..ad35e6481 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -147,10 +147,13 @@ private: [[nodiscard]] QSize photoSize() const; [[nodiscard]] QRect enlargeRect() const; + void paintPriceTag(Painter &p, QRect rthumb) const; + [[nodiscard]] ClickHandlerPtr ensureExtendedMediaLink() const; void togglePollingStory(bool enabled) const; const not_null _data; const FullStoryId _storyId; + mutable ClickHandlerPtr _extendedMediaLink; mutable std::shared_ptr _dataMedia; mutable std::unique_ptr _streamed; const std::unique_ptr _spoiler; diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index 99d9b4288..fe1a7b5b6 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -638,6 +638,7 @@ storiesEmojiPan: EmojiPan(defaultEmojiPan) { menuSpoilerOff: icon {{ "menu/spoiler_off", storiesComposeWhiteText }}; menuBelow: icon {{ "menu/link_below", storiesComposeWhiteText }}; menuAbove: icon {{ "menu/link_above", storiesComposeWhiteText }}; + menuPrice: icon {{ "menu/earn", storiesComposeWhiteText }}; stripBubble: icon{ { "chat/reactions_bubble_shadow", windowShadowFg }, diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 58328da12..2ebfba197 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -678,6 +678,14 @@ FillMenuResult FillSendMenu( }, details); }, above ? &icons.menuBelow : &icons.menuAbove); } + if (details.price) { + menu->addAction( + ((*details.price > 0) + ? tr::lng_context_change_price(tr::now) + : tr::lng_context_make_paid(tr::now)), + [=] { action({ .type = ActionType::ChangePrice }, details); }, + &icons.menuPrice); + } using namespace HistoryView::Reactions; const auto effect = std::make_shared>(); diff --git a/Telegram/SourceFiles/menu/menu_send.h b/Telegram/SourceFiles/menu/menu_send.h index 2c6dbb9b7..5c3a99bae 100644 --- a/Telegram/SourceFiles/menu/menu_send.h +++ b/Telegram/SourceFiles/menu/menu_send.h @@ -53,6 +53,7 @@ struct Details { Type type = Type::Disabled; SpoilerState spoiler = SpoilerState::None; CaptionState caption = CaptionState::None; + std::optional price; bool effectAllowed = false; }; @@ -69,6 +70,7 @@ enum class ActionType : uchar { SpoilerOff, CaptionUp, CaptionDown, + ChangePrice, }; struct Action { using Type = ActionType; diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp index 112cdd576..aad25cdf5 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -27,7 +27,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" namespace Payments { -namespace { bool IsCreditsInvoice(not_null item) { if (const auto payment = item->Get()) { @@ -38,8 +37,6 @@ bool IsCreditsInvoice(not_null item) { return invoice && (invoice->currency == Ui::kCreditsCurrency); } -} // namespace - Fn ProcessNonPanelPaymentFormFactory( not_null controller, Fn maybeReturnToBot) { diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.h b/Telegram/SourceFiles/payments/payments_non_panel_process.h index fb647a72a..e8ab9375c 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.h +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.h @@ -18,6 +18,8 @@ namespace Payments { enum class CheckoutResult; struct NonPanelPaymentForm; +[[nodiscard]] bool IsCreditsInvoice(not_null item); + Fn ProcessNonPanelPaymentFormFactory( not_null controller, Fn maybeReturnToBot = nullptr); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp index 01b94a9d1..b142a61c1 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp @@ -32,9 +32,11 @@ constexpr auto kMinPreviewWidth = 20; AbstractSingleMediaPreview::AbstractSingleMediaPreview( QWidget *parent, const style::ComposeControls &st, - AttachControls::Type type) + AttachControls::Type type, + Fn canToggleSpoiler) : AbstractSinglePreview(parent) , _st(st) +, _canToggleSpoiler(std::move(canToggleSpoiler)) , _minThumbH(st::sendBoxAlbumGroupSize.height() + st::sendBoxAlbumGroupSkipTop * 2) , _controls(base::make_unique_q(this, type)) { @@ -266,7 +268,9 @@ void AbstractSingleMediaPreview::applyCursor(style::cursor cursor) { } void AbstractSingleMediaPreview::showContextMenu(QPoint position) { - if (!_sendWay.sendImagesAsPhotos() || !supportsSpoilers()) { + if (!_canToggleSpoiler() + || !_sendWay.sendImagesAsPhotos() + || !supportsSpoilers()) { return; } _menu = base::make_unique_q( diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h index 0f5f8e784..fd72facf1 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h @@ -26,7 +26,8 @@ public: AbstractSingleMediaPreview( QWidget *parent, const style::ComposeControls &st, - AttachControls::Type type); + AttachControls::Type type, + Fn canToggleSpoiler); ~AbstractSingleMediaPreview(); void setSendWay(SendFilesWay way); @@ -71,6 +72,7 @@ private: const style::ComposeControls &_st; SendFilesWay _sendWay; + Fn _canToggleSpoiler; bool _animated = false; QPixmap _preview; QPixmap _previewBlurred; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp index 9b842aae5..76ff1ab2b 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp @@ -31,10 +31,12 @@ AlbumPreview::AlbumPreview( QWidget *parent, const style::ComposeControls &st, gsl::span items, - SendFilesWay way) + SendFilesWay way, + Fn canToggleSpoiler) : RpWidget(parent) , _st(st) , _sendWay(way) +, _canToggleSpoiler(std::move(canToggleSpoiler)) , _dragTimer([=] { switchToDrag(); }) { setMouseTracking(true); prepareThumbs(items); @@ -573,7 +575,7 @@ void AlbumPreview::mouseReleaseEvent(QMouseEvent *e) { void AlbumPreview::showContextMenu( not_null thumb, QPoint position) { - if (!_sendWay.sendImagesAsPhotos()) { + if (!_canToggleSpoiler() || !_sendWay.sendImagesAsPhotos()) { return; } _menu = base::make_unique_q( diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h index 11eda122f..f51eb26e9 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h @@ -28,7 +28,8 @@ public: QWidget *parent, const style::ComposeControls &st, gsl::span items, - SendFilesWay way); + SendFilesWay way, + Fn canToggleSpoiler); ~AlbumPreview(); void setSendWay(SendFilesWay way); @@ -92,6 +93,7 @@ private: const style::ComposeControls &_st; SendFilesWay _sendWay; + Fn _canToggleSpoiler; style::cursor _cursor = style::cur_default; std::vector _order; std::vector _itemsShownDimensions; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.cpp index b686ee6fe..d180059dd 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.cpp @@ -36,7 +36,7 @@ ItemSingleMediaPreview::ItemSingleMediaPreview( Fn gifPaused, not_null item, AttachControls::Type type) -: AbstractSingleMediaPreview(parent, st, type) +: AbstractSingleMediaPreview(parent, st, type, [] { return true; }) , _gifPaused(std::move(gifPaused)) , _fullId(item->fullId()) { const auto media = item->media(); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp index 1a70c69b6..e3f90912f 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp @@ -195,6 +195,10 @@ bool PreparedList::canMoveCaption(bool sendingAlbum, bool compress) const { || (file.type == PreparedFile::Type::Photo && compress); } +bool PreparedList::canChangePrice(bool sendingAlbum, bool compress) const { + return canMoveCaption(sendingAlbum, compress); +} + bool PreparedList::hasGroupOption(bool slowmode) const { if (slowmode || files.size() < 2) { return false; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h index 30210e3d6..49fd71fa1 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h @@ -115,6 +115,9 @@ struct PreparedList { [[nodiscard]] bool canMoveCaption( bool sendingAlbum, bool compress) const; + [[nodiscard]] bool canChangePrice( + bool sendingAlbum, + bool compress) const; [[nodiscard]] bool canBeSentInSlowmode() const; [[nodiscard]] bool canBeSentInSlowmodeWith( const PreparedList &other) const; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp index ed250540c..cb80676cd 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp @@ -19,6 +19,7 @@ SingleMediaPreview *SingleMediaPreview::Create( const style::ComposeControls &st, Fn gifPaused, const PreparedFile &file, + Fn canToggleSpoiler, AttachControls::Type type) { auto preview = QImage(); auto animated = false; @@ -51,7 +52,8 @@ SingleMediaPreview *SingleMediaPreview::Create( Core::IsMimeSticker(file.information->filemime), file.spoiler, animationPreview ? file.path : QString(), - type); + type, + std::move(canToggleSpoiler)); } SingleMediaPreview::SingleMediaPreview( @@ -63,8 +65,9 @@ SingleMediaPreview::SingleMediaPreview( bool sticker, bool spoiler, const QString &animatedPreviewPath, - AttachControls::Type type) -: AbstractSingleMediaPreview(parent, st, type) + AttachControls::Type type, + Fn canToggleSpoiler) +: AbstractSingleMediaPreview(parent, st, type, std::move(canToggleSpoiler)) , _gifPaused(std::move(gifPaused)) , _sticker(sticker) { Expects(!preview.isNull()); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.h index ac5d4f84f..d57ccd366 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.h @@ -25,6 +25,7 @@ public: const style::ComposeControls &st, Fn gifPaused, const PreparedFile &file, + Fn canToggleSpoiler, AttachControls::Type type = AttachControls::Type::Full); SingleMediaPreview( @@ -36,7 +37,8 @@ public: bool sticker, bool spoiler, const QString &animatedPreviewPath, - AttachControls::Type type); + AttachControls::Type type, + Fn canToggleSpoiler); protected: bool supportsSpoilers() const override; From 5f8da27c86b734a83531871af0ab01132fcebc58 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 19 Jun 2024 13:39:02 +0400 Subject: [PATCH 077/142] Display nice price tag, handle pay in albums. --- Telegram/SourceFiles/apiwrap.cpp | 76 ++++++++- Telegram/SourceFiles/apiwrap.h | 6 + Telegram/SourceFiles/boxes/send_files_box.cpp | 10 +- .../chat_helpers/message_field.cpp | 4 +- .../history/view/media/history_view_media.h | 14 ++ .../view/media/history_view_media_grouped.cpp | 53 ++++++ .../view/media/history_view_media_grouped.h | 3 + .../history/view/media/history_view_photo.cpp | 154 +++++++++++++----- .../history/view/media/history_view_photo.h | 14 +- 9 files changed, 288 insertions(+), 46 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 424c8ccc1..86481cec7 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -4212,6 +4212,67 @@ void ApiWrap::sendMediaWithRandomId( }); } +void ApiWrap::sendMultiPaidMedia( + not_null item, + const QVector &medias, + Api::SendOptions options, + uint64 randomId, + Fn done) { + Expects(options.price > 0); + + const auto history = item->history(); + const auto replyTo = item->replyTo(); + + auto caption = item->originalText(); + TextUtilities::Trim(caption); + auto sentEntities = Api::EntitiesToMTP( + _session, + caption.entities, + Api::ConvertOption::SkipLocal); + + using Flag = MTPmessages_SendMedia::Flag; + const auto flags = Flag(0) + | (replyTo ? Flag::f_reply_to : Flag(0)) + | (ShouldSendSilent(history->peer, options) + ? Flag::f_silent + : Flag(0)) + | (!sentEntities.v.isEmpty() ? Flag::f_entities : Flag(0)) + | (options.scheduled ? Flag::f_schedule_date : Flag(0)) + | (options.sendAs ? Flag::f_send_as : Flag(0)) + | (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)) + | (options.effectId ? Flag::f_effect : Flag(0)) + | (options.invertCaption ? Flag::f_invert_media : Flag(0)); + + auto &histories = history->owner().histories(); + const auto peer = history->peer; + const auto itemId = item->fullId(); + histories.sendPreparedMessage( + history, + replyTo, + randomId, + Data::Histories::PrepareMessage( + MTP_flags(flags), + peer->input, + Data::Histories::ReplyToPlaceholder(), + MTP_inputMediaPaidMedia( + MTP_long(options.price), + MTP_vector(medias)), + MTP_string(caption.text), + MTP_long(randomId), + MTPReplyMarkup(), + sentEntities, + MTP_int(options.scheduled), + (options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()), + Data::ShortcutIdToMTP(_session, options.shortcutId), + MTP_long(options.effectId) + ), [=](const MTPUpdates &result, const MTP::Response &response) { + if (done) done(true); + }, [=](const MTP::Error &error, const MTP::Response &response) { + if (done) done(false); + sendMessageFail(error, peer, randomId, itemId); + }); +} + void ApiWrap::sendAlbumWithUploaded( not_null item, const MessageGroupId &groupId, @@ -4266,7 +4327,7 @@ void ApiWrap::sendAlbumIfReady(not_null album) { _sendingAlbums.remove(groupId); return; } else if (medias.size() < 2) { - const auto &single = medias.front().c_inputSingleMedia(); + const auto &single = medias.front().data(); sendMediaWithRandomId( sample, single.vmedia(), @@ -4274,6 +4335,19 @@ void ApiWrap::sendAlbumIfReady(not_null album) { single.vrandom_id().v); _sendingAlbums.remove(groupId); return; + } else if (album->options.price > 0) { + const auto &single = medias.front().data(); + auto list = medias | ranges::view::transform([]( + const MTPInputSingleMedia &media) { + return MTPInputMedia(media.data().vmedia()); + }) | ranges::to>(); + sendMultiPaidMedia( + sample, + std::move(list), + album->options, + single.vrandom_id().v); + _sendingAlbums.remove(groupId); + return; } const auto history = sample->history(); const auto replyTo = sample->replyTo(); diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 9710ee1bd..3904eb3d3 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -545,6 +545,12 @@ private: Api::SendOptions options, uint64 randomId, Fn done = nullptr); + void sendMultiPaidMedia( + not_null item, + const QVector &medias, + Api::SendOptions options, + uint64 randomId, + Fn done = nullptr); void getTopPromotionDelayed(TimeId now, TimeId next); void topPromotionDone(const MTPhelp_PromoData &proxy); diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index f80279345..ac27ddb0f 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -680,9 +680,11 @@ bool SendFilesBox::hasSpoilerMenu() const { bool SendFilesBox::canChangePrice() const { const auto way = _sendWay.current(); - return _list.canChangePrice( - way.groupFiles() && way.sendImagesAsPhotos(), - way.sendImagesAsPhotos()); + return _captionToPeer + && _captionToPeer->isBroadcast() + && _list.canChangePrice( + way.groupFiles() && way.sendImagesAsPhotos(), + way.sendImagesAsPhotos()); } void SendFilesBox::applyBlockChanges() { @@ -754,6 +756,7 @@ void SendFilesBox::refreshPriceTag() { raw, tr::lng_paid_price(lt_price, std::move(price)), st::paidTagLabel); + label->show(); label->sizeValue() | rpl::start_with_next([=](QSize size) { const auto inner = QRect(QPoint(), size); const auto rect = inner.marginsAdded(st::paidTagPadding); @@ -833,6 +836,7 @@ void SendFilesBox::initSendWay() { block.setSendWay(value); } refreshButtons(); + refreshPriceTag(); if (was != hidden()) { updateBoxSize(); updateControlsGeometry(); diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 38435758e..5d8be6144 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -1186,7 +1186,9 @@ base::unique_qptr PremiumRequiredSendRestriction( const auto margins = (st.textMargins + st.placeholderMargins); const auto available = width - margins.left() - margins.right(); label->resizeToWidth(available); - label->moveToLeft(margins.left(), margins.top(), width); + const auto height = label->height() + link->height(); + const auto top = (raw->height() - height) / 2; + label->moveToLeft(margins.left(), top, width); link->move( (width - link->width()) / 2, label->y() + label->height()); diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index 4926ca7c8..9d577255c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -223,6 +223,20 @@ public: QPoint point, StateRequest request) const; + virtual void drawPriceTag( + Painter &p, + QRect rthumb, + const PaintContext &context, + Fn generateBackground) const { + Unexpected("Price tag method call."); + } + [[nodiscard]] virtual ClickHandlerPtr priceTagLink() const { + Unexpected("Price tag method call."); + } + [[nodiscard]] virtual QImage priceTagBackground() const { + Unexpected("Price tag method call."); + } + [[nodiscard]] virtual bool animating() const { return false; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 6a67bf426..9f93e7203 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "storage/storage_shared_media.h" #include "lang/lang_keys.h" +#include "media/streaming/media_streaming_utility.h" #include "ui/grouped_layout.h" #include "ui/chat/chat_style.h" #include "ui/chat/message_bubble.h" @@ -290,6 +291,42 @@ QMargins GroupedMedia::groupedPadding() const { (normal.bottom() - grouped.bottom()) + addToBottom); } +Media *GroupedMedia::lookupUnpaidMedia() const { + if (_parts.empty()) { + return nullptr; + } + const auto media = _parts.front().content.get(); + const auto photo = media ? media->getPhoto() : nullptr; + return (photo && photo->extendedMediaPreview()) ? media : nullptr; +} + +QImage GroupedMedia::generatePriceTagBackground(QRect full) const { + const auto ratio = style::DevicePixelRatio(); + auto result = QImage( + full.size() * ratio, + QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(ratio); + auto p = QPainter(&result); + const auto shift = -full.topLeft(); + const auto skip1 = st::historyGroupSkip / 2; + const auto skip2 = st::historyGroupSkip - skip1; + for (const auto &part : _parts) { + auto background = part.content->priceTagBackground(); + const auto extended = part.geometry.translated(shift).marginsAdded( + { skip1, skip1, skip2, skip2 }); + if (background.isNull()) { + p.fillRect(extended, Qt::black); + } else { + p.drawImage(extended, background); + } + } + p.end(); + + return ::Media::Streaming::PrepareBlurredBackground( + full.size(), + std::move(result)); +} + void GroupedMedia::drawHighlight( Painter &p, const PaintContext &context, @@ -351,6 +388,8 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { ? Ui::BubbleRounding{ kSmall, kSmall, kSmall, kSmall } : adjustedBubbleRounding(); auto highlight = context.highlight.range; + const auto unpaid = lookupUnpaidMedia(); + auto fullRect = QRect(); const auto subpartHighlight = IsSubGroupSelection(highlight); for (auto i = 0, count = int(_parts.size()); i != count; ++i) { const auto &part = _parts[i]; @@ -390,11 +429,20 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { if (!part.cache.isNull()) { nowCache = true; } + if (unpaid) { + fullRect = fullRect.united(part.geometry); + } } if (nowCache && !wasCache) { history()->owner().registerHeavyViewPart(_parent); } + if (unpaid) { + unpaid->drawPriceTag(p, fullRect, context, [&] { + return generatePriceTagBackground(fullRect); + }); + } + // date if (_parent->media() == this && (!_parent->hasBubble() || isBubbleBottom())) { auto fullRight = width(); @@ -455,6 +503,11 @@ PointState GroupedMedia::pointState(QPoint point) const { TextState GroupedMedia::textState(QPoint point, StateRequest request) const { const auto groupPadding = groupedPadding(); auto result = getPartState(point - QPoint(0, groupPadding.top()), request); + if (const auto unpaid = lookupUnpaidMedia()) { + if (QRect(0, 0, width(), height()).contains(point)) { + result.link = unpaid->priceTagLink(); + } + } if (_parent->media() == this && (!_parent->hasBubble() || isBubbleBottom())) { auto fullRight = width(); auto fullBottom = height(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h index 84dca4884..b101fef91 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h @@ -149,6 +149,9 @@ private: RectParts sides) const; [[nodiscard]] QMargins groupedPadding() const; + [[nodiscard]] Media *lookupUnpaidMedia() const; + [[nodiscard]] QImage generatePriceTagBackground(QRect full) const; + mutable std::optional _captionItem; std::vector _parts; Mode _mode = Mode::Grid; diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 7650fcd9a..e3f6c91c5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -60,6 +60,14 @@ struct Photo::Streamed { QImage roundingMask; }; +struct Photo::PriceTag { + uint64 price = 0; + QImage cache; + QColor darken; + QColor fg; + ClickHandlerPtr link; +}; + Photo::Streamed::Streamed( std::shared_ptr<::Media::Streaming::Document> shared) : instance(std::move(shared), nullptr) { @@ -281,8 +289,8 @@ void Photo::draw(Painter &p, const PaintContext &context) const { const auto st = context.st; const auto sti = context.imageStyle(); const auto preview = _data->extendedMediaPreview(); - auto loaded = preview || _dataMedia->loaded(); - auto displayLoading = !preview && _data->displayLoading(); + const auto loaded = preview || _dataMedia->loaded(); + const auto displayLoading = !preview && _data->displayLoading(); auto inWebPage = (_parent->media() != this); auto paintx = 0, painty = 0, paintw = width(), painth = height(); @@ -370,7 +378,9 @@ void Photo::draw(Painter &p, const PaintContext &context) const { _animation->radial.draw(p, rinner, st::msgFileRadialLine, sti->historyFileThumbRadialFg); } } else if (preview) { - paintPriceTag(p, rthumb); + drawPriceTag(p, rthumb, context, [&] { + return _spoiler ? _spoiler->background : QImage(); + }); } if (showEnlarge) { auto hq = PainterHighQualityEnabler(p); @@ -403,41 +413,93 @@ void Photo::draw(Painter &p, const PaintContext &context) const { } } -void Photo::paintPriceTag(Painter &p, QRect rthumb) const { +void Photo::setupPriceTag() const { const auto media = parent()->data()->media(); const auto invoice = media ? media->invoice() : nullptr; const auto price = invoice->isPaidMedia ? invoice->amount : 0; if (!price) { return; } + _priceTag = std::make_unique(); + _priceTag->price = price; +} - auto text = Ui::Text::String(); - text.setText( - st::semiboldTextStyle, - tr::lng_paid_price( - tr::now, - lt_price, - QChar(0x2B50) + Lang::FormatCountDecimal(invoice->amount))); - const auto width = text.maxWidth(); - const auto inner = QRect(0, 0, width, text.minHeight()); - const auto outer = inner.marginsAdded(st::paidTagPadding); - const auto size = outer.size(); +void Photo::drawPriceTag( + Painter &p, + QRect rthumb, + const PaintContext &context, + Fn generateBackground) const { + if (!_priceTag) { + setupPriceTag(); + if (!_priceTag) { + return; + } + } + const auto st = context.st; + const auto darken = st->msgDateImgBg()->c; + const auto fg = st->msgDateImgFg()->c; + if (_priceTag->cache.isNull() + || _priceTag->darken != darken + || _priceTag->fg != fg) { + auto bg = generateBackground(); + if (bg.isNull()) { + bg = QImage(2, 2, QImage::Format_ARGB32_Premultiplied); + bg.fill(Qt::black); + } - auto hq = PainterHighQualityEnabler(p); - p.setBrush(st::toastBg); - p.setPen(Qt::NoPen); + auto text = Ui::Text::String(); + text.setText( + st::semiboldTextStyle, + tr::lng_paid_price( + tr::now, + lt_price, + QChar(0x2B50) + Lang::FormatCountDecimal(_priceTag->price))); + const auto width = text.maxWidth(); + const auto inner = QRect(0, 0, width, text.minHeight()); + const auto outer = inner.marginsAdded(st::paidTagPadding); + const auto size = outer.size(); + const auto radius = std::min(size.width(), size.height()) / 2; + const auto ratio = style::DevicePixelRatio(); + auto cache = QImage( + size * ratio, + QImage::Format_ARGB32_Premultiplied); + cache.setDevicePixelRatio(ratio); + cache.fill(Qt::black); + auto p = Painter(&cache); + auto hq = PainterHighQualityEnabler(p); + p.drawImage( + QRect( + (size.width() - rthumb.width()) / 2, + (size.height() - rthumb.height()) / 2, + rthumb.width(), + rthumb.height()), + bg); + p.fillRect(QRect(QPoint(), size), darken); + p.setPen(fg); + text.draw(p, -outer.x(), -outer.y(), width); + p.end(); - const auto radius = std::min(size.width(), size.height()) / 2.; - const auto rect = QRect( - rthumb.x() + (rthumb.width() - size.width()) / 2, - rthumb.y() + (rthumb.height() - size.height()) / 2, - size.width(), - size.height()); - - p.drawRoundedRect(rect, radius, radius); - p.setPen(st::toastFg); - - text.draw(p, rect.x() - outer.x(), rect.y() - outer.y(), width); + _priceTag->darken = darken; + _priceTag->fg = fg; + _priceTag->cache = Images::Round( + std::move(cache), + Images::CornersMask(radius)); + } + const auto &cache = _priceTag->cache; + const auto size = cache.size() / cache.devicePixelRatio(); + const auto left = rthumb.x() + (rthumb.width() - size.width()) / 2; + const auto top = rthumb.y() + (rthumb.height() - size.height()) / 2; + p.drawImage(left, top, cache); + if (context.selected()) { + auto hq = PainterHighQualityEnabler(p); + const auto radius = std::min(size.width(), size.height()) / 2; + p.setPen(Qt::NoPen); + p.setBrush(st->msgSelectOverlay()); + p.drawRoundedRect( + QRect(left, top, size.width(), size.height()), + radius, + radius); + } } void Photo::validateUserpicImageCache(QSize size, bool forum) const { @@ -647,12 +709,24 @@ QRect Photo::enlargeRect() const { }; } -ClickHandlerPtr Photo::ensureExtendedMediaLink() const { +ClickHandlerPtr Photo::priceTagLink() const { const auto item = parent()->data(); - if (!_extendedMediaLink && item->isRegular()) { - _extendedMediaLink = MakePaidMediaLink(item); + if (!item->isRegular()) { + return nullptr; + } else if (!_priceTag) { + setupPriceTag(); + if (!_priceTag) { + return nullptr; + } } - return _extendedMediaLink; + if (!_priceTag->link) { + _priceTag->link = MakePaidMediaLink(item); + } + return _priceTag->link; +} + +QImage Photo::priceTagBackground() const { + return _spoiler ? _spoiler->background : QImage(); } TextState Photo::textState(QPoint point, StateRequest request) const { @@ -669,7 +743,7 @@ TextState Photo::textState(QPoint point, StateRequest request) const { if (QRect(paintx, painty, paintw, painth).contains(point)) { ensureDataMediaCreated(); result.link = _data->extendedMediaPreview() - ? ensureExtendedMediaLink() + ? priceTagLink() : (_spoiler && !_spoiler->revealed) ? _spoiler->link : _data->uploading() @@ -735,8 +809,9 @@ void Photo::drawGrouped( const auto st = context.st; const auto sti = context.imageStyle(); - const auto loaded = _dataMedia->loaded(); - const auto displayLoading = _data->displayLoading(); + const auto preview = _data->extendedMediaPreview(); + const auto loaded = preview || _dataMedia->loaded(); + const auto displayLoading = !preview && _data->displayLoading(); if (displayLoading) { ensureAnimation(); @@ -841,7 +916,9 @@ TextState Photo::getStateGrouped( return {}; } ensureDataMediaCreated(); - return TextState(_parent, (_spoiler && !_spoiler->revealed) + return TextState(_parent, _data->extendedMediaPreview() + ? priceTagLink() + : (_spoiler && !_spoiler->revealed) ? _spoiler->link : _data->uploading() ? _cancell @@ -887,7 +964,8 @@ void Photo::validateGroupedCache( ensureDataMediaCreated(); - const auto loaded = _dataMedia->loaded(); + const auto preview = _data->extendedMediaPreview(); + const auto loaded = preview || _dataMedia->loaded(); const auto loadLevel = loaded ? 2 : (_dataMedia->thumbnailInline() diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index ad35e6481..223eae37e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -75,6 +75,14 @@ public: QPoint point, StateRequest request) const override; + void drawPriceTag( + Painter &p, + QRect rthumb, + const PaintContext &context, + Fn generateBackground) const override; + ClickHandlerPtr priceTagLink() const override; + QImage priceTagBackground() const override; + void hideSpoilers() override; bool needsBubble() const override; bool customInfoLayout() const override { @@ -97,6 +105,7 @@ protected: private: struct Streamed; + struct PriceTag; void create(FullMsgId contextId, PeerData *chat = nullptr); @@ -106,6 +115,7 @@ private: void ensureDataMediaCreated() const; void dataMediaCreated() const; + void setupPriceTag() const; QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; @@ -147,13 +157,11 @@ private: [[nodiscard]] QSize photoSize() const; [[nodiscard]] QRect enlargeRect() const; - void paintPriceTag(Painter &p, QRect rthumb) const; - [[nodiscard]] ClickHandlerPtr ensureExtendedMediaLink() const; void togglePollingStory(bool enabled) const; const not_null _data; const FullStoryId _storyId; - mutable ClickHandlerPtr _extendedMediaLink; + mutable std::unique_ptr _priceTag; mutable std::shared_ptr _dataMedia; mutable std::unique_ptr _streamed; const std::unique_ptr _spoiler; From 950a946a1650c3cc850033723bd3d621308d94c4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 19 Jun 2024 15:01:28 +0400 Subject: [PATCH 078/142] Show correct chat preview for paid media. --- Telegram/SourceFiles/apiwrap.cpp | 41 +++-- Telegram/SourceFiles/apiwrap.h | 4 +- Telegram/SourceFiles/data/data_groups.cpp | 1 + .../SourceFiles/data/data_media_types.cpp | 172 +++++++++++++++--- Telegram/SourceFiles/data/data_media_types.h | 2 + Telegram/SourceFiles/data/data_photo.cpp | 4 +- Telegram/SourceFiles/data/data_photo.h | 2 +- .../view/media/history_view_media_grouped.cpp | 6 +- Telegram/SourceFiles/menu/menu_send.cpp | 6 +- 9 files changed, 184 insertions(+), 54 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 86481cec7..04a2e25f1 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -4214,11 +4214,18 @@ void ApiWrap::sendMediaWithRandomId( void ApiWrap::sendMultiPaidMedia( not_null item, - const QVector &medias, - Api::SendOptions options, - uint64 randomId, + not_null album, Fn done) { - Expects(options.price > 0); + Expects(album->options.price > 0); + + const auto groupId = album->groupId; + const auto &options = album->options; + const auto randomId = album->items.front().randomId; + auto medias = album->items | ranges::view::transform([]( + const SendingAlbum::Item &part) { + Assert(part.media.has_value()); + return MTPInputMedia(part.media->data().vmedia()); + }) | ranges::to>(); const auto history = item->history(); const auto replyTo = item->replyTo(); @@ -4256,7 +4263,7 @@ void ApiWrap::sendMultiPaidMedia( Data::Histories::ReplyToPlaceholder(), MTP_inputMediaPaidMedia( MTP_long(options.price), - MTP_vector(medias)), + MTP_vector(std::move(medias))), MTP_string(caption.text), MTP_long(randomId), MTPReplyMarkup(), @@ -4266,6 +4273,14 @@ void ApiWrap::sendMultiPaidMedia( Data::ShortcutIdToMTP(_session, options.shortcutId), MTP_long(options.effectId) ), [=](const MTPUpdates &result, const MTP::Response &response) { + if (const auto album = _sendingAlbums.take(groupId)) { + const auto copy = (*album)->items; + for (const auto &part : copy) { + if (const auto item = history->owner().message(part.msgId)) { + item->destroy(); + } + } + } if (done) done(true); }, [=](const MTP::Error &error, const MTP::Response &response) { if (done) done(false); @@ -4326,6 +4341,9 @@ void ApiWrap::sendAlbumIfReady(not_null album) { if (!sample) { _sendingAlbums.remove(groupId); return; + } else if (album->options.price > 0) { + sendMultiPaidMedia(sample, album); + return; } else if (medias.size() < 2) { const auto &single = medias.front().data(); sendMediaWithRandomId( @@ -4335,19 +4353,6 @@ void ApiWrap::sendAlbumIfReady(not_null album) { single.vrandom_id().v); _sendingAlbums.remove(groupId); return; - } else if (album->options.price > 0) { - const auto &single = medias.front().data(); - auto list = medias | ranges::view::transform([]( - const MTPInputSingleMedia &media) { - return MTPInputMedia(media.data().vmedia()); - }) | ranges::to>(); - sendMultiPaidMedia( - sample, - std::move(list), - album->options, - single.vrandom_id().v); - _sendingAlbums.remove(groupId); - return; } const auto history = sample->history(); const auto replyTo = sample->replyTo(); diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 3904eb3d3..15c0941c3 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -547,9 +547,7 @@ private: Fn done = nullptr); void sendMultiPaidMedia( not_null item, - const QVector &medias, - Api::SendOptions options, - uint64 randomId, + not_null album, Fn done = nullptr); void getTopPromotionDelayed(TimeId now, TimeId next); diff --git a/Telegram/SourceFiles/data/data_groups.cpp b/Telegram/SourceFiles/data/data_groups.cpp index cf75482db..15bb0d820 100644 --- a/Telegram/SourceFiles/data/data_groups.cpp +++ b/Telegram/SourceFiles/data/data_groups.cpp @@ -81,6 +81,7 @@ void Groups::refreshMessage( bool justRefreshViews) { if (!isGrouped(item)) { unregisterMessage(item); + _data->requestItemViewRefresh(item); return; } if (!item->isRegular() && !item->isScheduled()) { diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 1a8b052db..9ccb6ea4e 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -85,6 +85,13 @@ constexpr auto kLoadingStoryPhotoId = PhotoId(0x7FFF'DEAD'FFFF'FFFFULL); using ItemPreview = HistoryView::ItemPreview; using ItemPreviewImage = HistoryView::ItemPreviewImage; +struct AlbumCounts { + int photos = 0; + int videos = 0; + int audios = 0; + int files = 0; +}; + [[nodiscard]] TextWithEntities WithCaptionNotificationText( const QString &attachType, const TextWithEntities &caption, @@ -166,7 +173,7 @@ template return (reinterpret_cast(data.get()) & ~1) | (spoiler ? 1 : 0); } -[[nodiscard]] ItemPreviewImage PreparePhotoPreview( +[[nodiscard]] ItemPreviewImage PreparePhotoPreviewImage( not_null item, const std::shared_ptr &media, ImageRoundRadius radius, @@ -182,14 +189,15 @@ template } const auto allowedToDownload = media->autoLoadThumbnailAllowed( item->history()->peer); - const auto cacheKey = allowedToDownload ? 0 : counted; + const auto spoilered = uint64(spoiler ? 1 : 0); + const auto cacheKey = allowedToDownload ? spoilered : counted; if (allowedToDownload) { media->owner()->load(PhotoSize::Small, item->fullId()); } if (const auto blurred = media->thumbnailInline()) { return { PreparePreviewImage(blurred, radius, spoiler), cacheKey }; } - return { QImage(), allowedToDownload ? 0 : cacheKey }; + return { QImage(), allowedToDownload ? spoilered : cacheKey }; } [[nodiscard]] ItemPreviewImage PrepareFilePreviewImage( @@ -208,10 +216,11 @@ template }; } document->loadThumbnail(item->fullId()); + const auto spoilered = uint64(spoiler ? 1 : 0); if (const auto blurred = media->thumbnailInline()) { - return { PreparePreviewImage(blurred, radius, spoiler), 0 }; + return { PreparePreviewImage(blurred, radius, spoiler), spoilered }; } - return { QImage(), 0 }; + return { QImage(), spoilered }; } [[nodiscard]] QImage PutPlayIcon(QImage preview) { @@ -226,6 +235,18 @@ template return preview; } +[[nodiscard]] ItemPreviewImage PreparePhotoPreview( + not_null item, + const std::shared_ptr &media, + ImageRoundRadius radius, + bool spoiler) { + auto result = PreparePhotoPreviewImage(item, media, radius, spoiler); + if (media->owner()->extendedMediaVideoDuration().has_value()) { + result.data = PutPlayIcon(std::move(result.data)); + } + return result; +} + [[nodiscard]] ItemPreviewImage PrepareFilePreview( not_null item, const std::shared_ptr &media, @@ -280,7 +301,7 @@ bool UpdateExtendedMedia( auto changed = false; auto size = QSize(); auto thumbnail = QByteArray(); - auto videoDuration = TimeId(); + auto videoDuration = std::optional(); if (const auto &w = data.vw()) { const auto &h = data.vh(); Assert(h.has_value()); @@ -302,6 +323,8 @@ bool UpdateExtendedMedia( if (photo->extendedMediaVideoDuration() != videoDuration) { changed = true; } + } else if (photo->extendedMediaVideoDuration().has_value()) { + changed = true; } if (changed) { photo->setExtendedMediaPreview(size, thumbnail, videoDuration); @@ -355,6 +378,29 @@ TextForMimeData WithCaptionClipboardText( return result; } +[[nodiscard]] QString ComputeAlbumCountsString(AlbumCounts counts) { + const auto medias = counts.photos + counts.videos; + return (counts.photos && counts.videos) + ? tr::lng_in_dlg_media_count(tr::now, lt_count, medias) + : (counts.photos > 1) + ? tr::lng_in_dlg_photo_count(tr::now, lt_count, counts.photos) + : counts.photos + ? tr::lng_in_dlg_photo(tr::now) + : (counts.videos > 1) + ? tr::lng_in_dlg_video_count(tr::now, lt_count, counts.videos) + : counts.videos + ? tr::lng_in_dlg_video(tr::now) + : (counts.audios > 1) + ? tr::lng_in_dlg_audio_count(tr::now, lt_count, counts.audios) + : counts.audios + ? tr::lng_in_dlg_audio(tr::now) + : (counts.files > 1) + ? tr::lng_in_dlg_file_count(tr::now, lt_count, counts.files) + : counts.files + ? tr::lng_in_dlg_file(tr::now) + : tr::lng_in_dlg_album(tr::now); +} + } // namespace Invoice ComputeInvoiceData( @@ -481,6 +527,15 @@ bool HasUnpaidMedia(const Invoice &invoice) { return false; } +bool IsFirstVideo(const Invoice &invoice) { + if (invoice.extendedMedia.empty()) { + return false; + } else if (const auto photo = invoice.extendedMedia.front()->photo()) { + return photo->extendedMediaVideoDuration().has_value(); + } + return true; +} + Media::Media(not_null parent) : _parent(parent) { } @@ -643,21 +698,18 @@ ItemPreview Media::toGroupPreview( ToPreviewOptions options) const { auto result = ItemPreview(); auto loadingContext = std::vector(); - auto photoCount = 0; - auto videoCount = 0; - auto audioCount = 0; - auto fileCount = 0; + auto counts = AlbumCounts(); auto manyCaptions = false; for (const auto &item : items) { if (const auto media = item->media()) { if (media->photo()) { - photoCount++; + counts.photos++; } else if (const auto document = media->document()) { (document->isVideoFile() - ? videoCount + ? counts.videos : document->isAudioFile() - ? audioCount - : fileCount)++; + ? counts.audios + : counts.files)++; } auto copy = options; copy.ignoreGroup = true; @@ -687,19 +739,7 @@ ItemPreview Media::toGroupPreview( } } if (manyCaptions || result.text.text.isEmpty()) { - const auto mediaCount = photoCount + videoCount; - auto genericText = (photoCount && videoCount) - ? tr::lng_in_dlg_media_count(tr::now, lt_count, mediaCount) - : photoCount - ? tr::lng_in_dlg_photo_count(tr::now, lt_count, photoCount) - : videoCount - ? tr::lng_in_dlg_video_count(tr::now, lt_count, videoCount) - : audioCount - ? tr::lng_in_dlg_audio_count(tr::now, lt_count, audioCount) - : fileCount - ? tr::lng_in_dlg_file_count(tr::now, lt_count, fileCount) - : tr::lng_in_dlg_album(tr::now); - result.text = Ui::Text::Colorized(genericText); + result.text = Ui::Text::Colorized(ComputeAlbumCountsString(counts)); } if (!loadingContext.empty()) { result.loadingContext = std::move(loadingContext); @@ -1952,9 +1992,87 @@ bool MediaInvoice::replyPreviewLoaded() const { } TextWithEntities MediaInvoice::notificationText() const { + if (_invoice.isPaidMedia && !_invoice.extendedMedia.empty()) { + return WithCaptionNotificationText( + (IsFirstVideo(_invoice) + ? tr::lng_in_dlg_video + : tr::lng_in_dlg_photo)(tr::now), + parent()->originalText()); + } return { .text = _invoice.title }; } +ItemPreview MediaInvoice::toPreview(ToPreviewOptions options) const { + if (!_invoice.isPaidMedia || _invoice.extendedMedia.empty()) { + return Media::toPreview(options); + } + auto counts = AlbumCounts(); + const auto item = parent(); + auto images = std::vector(); + auto context = std::vector(); + const auto existing = options.existing; + const auto spoiler = HasUnpaidMedia(_invoice); + for (const auto &media : _invoice.extendedMedia) { + const auto raw = media.get(); + const auto photo = raw->photo(); + const auto document = raw->document(); + if (!photo && !document) { + continue; + } else if (images.size() < kMaxPreviewImages) { + auto found = photo + ? FindCachedPreview(existing, not_null(photo), spoiler) + : FindCachedPreview(existing, not_null(document), spoiler); + const auto radius = ImageRoundRadius::Small; + if (found) { + images.push_back(std::move(found)); + } else if (photo) { + const auto media = photo->createMediaView(); + if (auto prepared = PreparePhotoPreview( + parent(), + media, + radius, + spoiler) + ; prepared || !prepared.cacheKey) { + images.push_back(std::move(prepared)); + if (!prepared.cacheKey) { + context.push_back(media); + } + } + } else if (TryFilePreview(document)) { + const auto media = document->createMediaView(); + if (auto prepared = PrepareFilePreview( + parent(), + media, + radius, + spoiler) + ; prepared || !prepared.cacheKey) { + images.push_back(std::move(prepared)); + if (!prepared.cacheKey) { + context.push_back(media); + } + } + } + } + if (photo && !photo->extendedMediaVideoDuration().has_value()) { + ++counts.photos; + } else { + ++counts.videos; + } + } + const auto type = ComputeAlbumCountsString(counts); + const auto caption = (options.hideCaption || options.ignoreMessageText) + ? TextWithEntities() + : options.translated + ? parent()->translatedText() + : parent()->originalText(); + const auto hasMiniImages = !images.empty(); + return { + .text = WithCaptionNotificationText(type, caption, hasMiniImages), + .images = std::move(images), + .loadingContext = std::move(context), + }; +} + QString MediaInvoice::pinnedTextSubstring() const { return QString::fromUtf8("\xC2\xAB") + _invoice.title diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 62d03c53d..424e2c448 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -99,6 +99,7 @@ struct Invoice { }; [[nodiscard]] bool HasExtendedMedia(const Invoice &invoice); [[nodiscard]] bool HasUnpaidMedia(const Invoice &invoice); +[[nodiscard]] bool IsFirstVideo(const Invoice &invoice); struct GiveawayStart { std::vector> channels; @@ -506,6 +507,7 @@ public: Image *replyPreview() const override; bool replyPreviewLoaded() const override; TextWithEntities notificationText() const override; + ItemPreview toPreview(ToPreviewOptions way) const override; QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; diff --git a/Telegram/SourceFiles/data/data_photo.cpp b/Telegram/SourceFiles/data/data_photo.cpp index 35186750b..869bbf733 100644 --- a/Telegram/SourceFiles/data/data_photo.cpp +++ b/Telegram/SourceFiles/data/data_photo.cpp @@ -59,7 +59,7 @@ void PhotoData::setFields(TimeId date, bool hasAttachedStickers) { void PhotoData::setExtendedMediaPreview( QSize dimensions, const QByteArray &inlineThumbnailBytes, - TimeId videoDuration) { + std::optional videoDuration) { _extendedMediaPreview = true; updateImages( inlineThumbnailBytes, @@ -69,7 +69,7 @@ void PhotoData::setExtendedMediaPreview( {}, {}, {}); - _dateOrExtendedVideoDuration = videoDuration + 1; + _dateOrExtendedVideoDuration = videoDuration ? (*videoDuration + 1) : 0; } bool PhotoData::extendedMediaPreview() const { diff --git a/Telegram/SourceFiles/data/data_photo.h b/Telegram/SourceFiles/data/data_photo.h index 98166d265..3cd749a71 100644 --- a/Telegram/SourceFiles/data/data_photo.h +++ b/Telegram/SourceFiles/data/data_photo.h @@ -94,7 +94,7 @@ public: void setExtendedMediaPreview( QSize dimensions, const QByteArray &inlineThumbnailBytes, - TimeId videoDuration); + std::optional videoDuration); [[nodiscard]] bool extendedMediaPreview() const; [[nodiscard]] std::optional extendedMediaVideoDuration() const; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 9f93e7203..16d9d8080 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -104,7 +104,11 @@ HistoryItem *GroupedMedia::itemForText() const { auto result = (HistoryItem*)nullptr; for (const auto &part : _parts) { if (!part.item->emptyText()) { - if (result) { + if (result == part.item) { + // All parts are from the same message, that means + // this is an album with a single item, single text. + return result; + } else if (result) { return nullptr; } else { result = part.item; diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 2ebfba197..fb03be230 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -618,7 +618,8 @@ FillMenuResult FillSendMenu( const auto sending = (type != Type::Disabled); const auto empty = !sending && (details.spoiler == SpoilerState::None) - && (details.caption == CaptionState::None); + && (details.caption == CaptionState::None) + && !details.price.has_value(); if (empty || !action) { return FillMenuResult::Skipped; } @@ -651,7 +652,8 @@ FillMenuResult FillSendMenu( if ((type != Type::Disabled) && ((details.spoiler != SpoilerState::None) - || (details.caption != CaptionState::None))) { + || (details.caption != CaptionState::None) + || details.price.has_value())) { menu->addSeparator(&st::expandedMenuSeparator); } if (details.spoiler != SpoilerState::None) { From 479b63c33a2d25242d83b546c2f9a6832fe52901 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 19 Jun 2024 20:57:13 +0400 Subject: [PATCH 079/142] Customize pay-by-stars box for paid media. --- Telegram/Resources/langs/lang.strings | 12 ++ .../SourceFiles/boxes/send_credits_box.cpp | 162 +++++++++++++++--- .../SourceFiles/payments/payments_form.cpp | 1 + Telegram/SourceFiles/payments/payments_form.h | 1 + .../settings/settings_credits_graphics.cpp | 21 +++ .../settings/settings_credits_graphics.h | 5 + Telegram/SourceFiles/ui/effects/credits.style | 5 +- .../ui/effects/credits_graphics.cpp | 45 +++++ .../SourceFiles/ui/effects/credits_graphics.h | 4 + 9 files changed, 229 insertions(+), 27 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f4d2c145d..b8a0d6af7 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2327,9 +2327,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_box_out_title" = "Confirm Your Purchase"; "lng_credits_box_out_sure#one" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Star**?"; "lng_credits_box_out_sure#other" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Stars**?"; +"lng_credits_box_out_media#one" = "Do you want to unlock {media} in {chat} for **{count} Star**?"; +"lng_credits_box_out_media#other" = "Do you want to unlock {media} in {chat} for **{count} Stars**?"; +"lng_credits_box_out_photo" = "a photo"; +"lng_credits_box_out_photos#one" = "{count} photo"; +"lng_credits_box_out_photos#other" = "{count} photos"; +"lng_credits_box_out_video" = "a video"; +"lng_credits_box_out_videos#one" = "{count} video"; +"lng_credits_box_out_videos#other" = "{count} videos"; +"lng_credits_box_out_both" = "{photo} and {video}"; "lng_credits_box_out_confirm#one" = "Confirm and Pay {emoji} {count} Star"; "lng_credits_box_out_confirm#other" = "Confirm and Pay {emoji} {count} Stars"; "lng_credits_box_out_about" = "Review the {link} for Stars."; +"lng_credits_media_done_title" = "Media Unlocked"; +"lng_credits_media_done_text#one" = "**{count} Star** transferred to {chat}."; +"lng_credits_media_done_text#other" = "**{count} Stars** transferred to {chat}."; "lng_credits_summary_in_toast_title" = "Stars Acquired"; "lng_credits_summary_in_toast_about#one" = "**{count}** Star added to your balance."; "lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance."; diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index 8820c0c65..6e75f5c5c 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "core/ui_integration.h" // Core::MarkedTextContext. #include "data/data_credits.h" +#include "data/data_photo.h" #include "data/data_session.h" #include "data/data_user.h" #include "data/stickers/data_custom_emoji.h" @@ -39,6 +40,136 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_settings.h" namespace Ui { +namespace { + +struct PaidMediaData { + const Data::Invoice *invoice = nullptr; + HistoryItem *item = nullptr; + PeerData *peer = nullptr; + int photos = 0; + int videos = 0; + + explicit operator bool() const { + return invoice && item && peer && (photos || videos); + } +}; + +[[nodiscard]] PaidMediaData LookupPaidMediaData( + not_null session, + not_null form) { + using namespace Payments; + const auto message = std::get_if(&form->id.value); + const auto item = message + ? session->data().message(message->peer, message->itemId) + : nullptr; + const auto media = item ? item->media() : nullptr; + const auto invoice = media ? media->invoice() : nullptr; + if (!invoice || !invoice->isPaidMedia) { + return {}; + } + + auto photos = 0; + auto videos = 0; + for (const auto &media : invoice->extendedMedia) { + const auto photo = media->photo(); + if (photo && !photo->extendedMediaVideoDuration().has_value()) { + ++photos; + } else { + ++videos; + } + } + + return { + .invoice = invoice, + .item = item, + .peer = message->peer, + .photos = photos, + .videos = videos, + }; +} + +[[nodiscard]] rpl::producer SendCreditsConfirmText( + not_null session, + not_null form) { + if (const auto data = LookupPaidMediaData(session, form)) { + auto photos = 0; + auto videos = 0; + for (const auto &media : data.invoice->extendedMedia) { + const auto photo = media->photo(); + if (photo && !photo->extendedMediaVideoDuration().has_value()) { + ++photos; + } else { + ++videos; + } + } + + auto photosBold = tr::lng_credits_box_out_photos( + lt_count, + rpl::single(photos) | tr::to_count(), + Ui::Text::Bold); + auto videosBold = tr::lng_credits_box_out_videos( + lt_count, + rpl::single(videos) | tr::to_count(), + Ui::Text::Bold); + auto media = (!videos) + ? ((photos > 1) + ? std::move(photosBold) + : tr::lng_credits_box_out_photo(Ui::Text::WithEntities)) + : (!photos) + ? ((videos > 1) + ? std::move(videosBold) + : tr::lng_credits_box_out_video(Ui::Text::WithEntities)) + : tr::lng_credits_box_out_both( + lt_photo, + std::move(photosBold), + lt_video, + std::move(videosBold), + Ui::Text::WithEntities); + return tr::lng_credits_box_out_media( + lt_count, + rpl::single(form->invoice.amount) | tr::to_count(), + lt_media, + std::move(media), + lt_chat, + rpl::single(Ui::Text::Bold(data.peer->name())), + Ui::Text::RichLangValue); + } + + const auto bot = session->data().user(form->botId); + return tr::lng_credits_box_out_sure( + lt_count, + rpl::single(form->invoice.amount) | tr::to_count(), + lt_text, + rpl::single(TextWithEntities{ form->title }), + lt_bot, + rpl::single(TextWithEntities{ bot->name() }), + Ui::Text::RichLangValue); +} + +[[nodiscard]] object_ptr SendCreditsThumbnail( + not_null parent, + not_null session, + not_null form, + int photoSize) { + if (const auto data = LookupPaidMediaData(session, form)) { + const auto first = data.invoice->extendedMedia.front().get(); + if (const auto photo = first->photo()) { + if (photo->extendedMediaPreview()) { + return Settings::PaidMediaPhoto(parent, photo, photoSize); + } + } + } + if (form->photo) { + return Settings::HistoryEntryPhoto(parent, form->photo, photoSize); + } + const auto bot = session->data().user(form->botId); + return object_ptr( + parent, + bot, + st::defaultUserpicButton); +} + +} // namespace void SendCreditsBox( not_null box, @@ -89,22 +220,10 @@ void SendCreditsBox( }, ministarsContainer->lifetime()); } - const auto bot = session->data().user(form->botId); - - if (form->photo) { - box->addRow(object_ptr>( - content, - Settings::HistoryEntryPhoto(content, form->photo, photoSize))); - } else { - const auto widget = box->addRow( - object_ptr>( - content, - object_ptr( - content, - bot, - st::defaultUserpicButton))); - widget->setAttribute(Qt::WA_TransparentForMouseEvents); - } + const auto thumb = box->addRow(object_ptr>( + content, + SendCreditsThumbnail(content, session, form.get(), photoSize))); + thumb->setAttribute(Qt::WA_TransparentForMouseEvents); Ui::AddSkip(content); box->addRow(object_ptr>( @@ -118,14 +237,7 @@ void SendCreditsBox( box, object_ptr( box, - tr::lng_credits_box_out_sure( - lt_count, - rpl::single(form->invoice.amount) | tr::to_count(), - lt_text, - rpl::single(TextWithEntities{ form->title }), - lt_bot, - rpl::single(TextWithEntities{ bot->name() }), - Ui::Text::RichLangValue), + SendCreditsConfirmText(session, form.get()), st::creditsBoxAbout))); Ui::AddSkip(content); Ui::AddSkip(content); @@ -177,7 +289,7 @@ void SendCreditsBox( const auto buttonLabel = Ui::CreateChild( button, rpl::single(QString()), - st::defaultFlatLabel); + st::creditsBoxButtonLabel); std::move( buttonText ) | rpl::start_with_next([=](const TextWithEntities &text) { diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 8dac6d0c5..c09b41c36 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -399,6 +399,7 @@ void Form::requestForm() { .amount = amount, }; const auto formData = CreditsFormData{ + .id = _id, .formId = data.vform_id().v, .botId = data.vbot_id().v, .title = qs(data.vtitle()), diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index cf4cef890..c85d946ed 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -178,6 +178,7 @@ struct InvoiceId { }; struct CreditsFormData { + InvoiceId id; uint64 formId = 0; uint64 botId = 0; QString title; diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 4eec43924..63521ec31 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -544,6 +544,27 @@ object_ptr HistoryEntryPhoto( return owned; } +object_ptr PaidMediaPhoto( + not_null parent, + not_null photo, + int photoSize) { + auto owned = object_ptr(parent); + const auto widget = owned.data(); + widget->resize(Size(photoSize)); + + const auto draw = Ui::GeneratePaidMediaPaintCallback( + photo, + [=] { widget->update(); }); + + widget->paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(widget); + draw(p, 0, 0, photoSize, photoSize); + }, widget->lifetime()); + + return owned; +} + void SmallBalanceBox( not_null box, not_null controller, diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index d25b74688..d191187b3 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -50,6 +50,11 @@ void ReceiptCreditsBox( not_null photo, int photoSize); +[[nodiscard]] object_ptr PaidMediaPhoto( + not_null parent, + not_null photo, + int photoSize); + void SmallBalanceBox( not_null box, not_null controller, diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 2da64404d..1518b69b1 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -35,11 +35,12 @@ creditsBoxAbout: FlatLabel(defaultFlatLabel) { minWidth: 256px; align: align(top); } - creditsBoxAboutTitle: FlatLabel(settingsPremiumUserTitle) { minWidth: 256px; } - creditsBoxAboutDivider: FlatLabel(boxDividerLabel) { align: align(top); } +creditsBoxButtonLabel: FlatLabel(defaultFlatLabel) { + style: semiboldTextStyle; +} diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index ffc5a6503..ce257b936 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "main/main_session.h" #include "ui/effects/premium_graphics.h" +#include "ui/effects/spoiler_mess.h" #include "ui/empty_userpic.h" #include "ui/painter.h" #include "ui/rect.h" @@ -179,6 +180,50 @@ Fn GenerateCreditsPaintEntryCallback( }; } +Fn GeneratePaidMediaPaintCallback( + not_null photo, + Fn update) { + struct State { + explicit State(Fn update) : spoiler(std::move(update)) { + } + + QImage image; + Ui::SpoilerAnimation spoiler; + }; + const auto state = std::make_shared(update); + + return [=](Painter &p, int x, int y, int outerWidth, int size) { + if (state->image.isNull()) { + const auto media = photo->createMediaView(); + const auto thumbnail = media->thumbnailInline(); + const auto ratio = style::DevicePixelRatio(); + const auto scaled = QSize(size, size) * ratio; + auto image = thumbnail + ? Images::Blur(thumbnail->original(), true) + : QImage(scaled, QImage::Format_ARGB32_Premultiplied); + if (!thumbnail) { + image.fill(Qt::black); + image.setDevicePixelRatio(ratio); + } + const auto minSize = std::min(image.width(), image.height()); + state->image = Images::Prepare( + image.copy( + (image.width() - minSize) / 2, + (image.height() - minSize) / 2, + minSize, + minSize), + size * ratio, + { .options = Images::Option::RoundLarge }); + } + p.drawImage(x, y, state->image); + Ui::FillSpoilerRect( + p, + QRect(x, y, size, size), + Ui::DefaultImageSpoiler().frame( + state->spoiler.index(crl::now(), false))); + }; +} + TextWithEntities GenerateEntryName(const Data::CreditsHistoryEntry &entry) { return ((entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment) ? tr::lng_bot_username_description1_link diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.h b/Telegram/SourceFiles/ui/effects/credits_graphics.h index 1d0751605..a730cffec 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.h +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.h @@ -24,6 +24,10 @@ Fn GenerateCreditsPaintEntryCallback( not_null photo, Fn update); +Fn GeneratePaidMediaPaintCallback( + not_null photo, + Fn update); + [[nodiscard]] TextWithEntities GenerateEntryName( const Data::CreditsHistoryEntry &entry); From 6c1e7357c68f3d8a30f7c734d4707f5a7bf359fb Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Jun 2024 09:48:11 +0400 Subject: [PATCH 080/142] Nice unlock media stars, unlock done tooltip. --- .../SourceFiles/boxes/send_credits_box.cpp | 26 +++++++------- Telegram/SourceFiles/boxes/send_credits_box.h | 7 ++++ Telegram/SourceFiles/boxes/send_files_box.cpp | 28 ++++++++++++--- .../chat_helpers/chat_helpers.style | 3 ++ .../view/media/history_view_media_common.cpp | 36 +++++++++++++++++++ .../history/view/media/history_view_photo.cpp | 22 ++++++++++-- Telegram/SourceFiles/ui/chat/chat.style | 3 ++ Telegram/SourceFiles/ui/chat/chat_style.cpp | 1 + Telegram/SourceFiles/ui/chat/chat_style.h | 4 +++ 9 files changed, 111 insertions(+), 19 deletions(-) diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index 6e75f5c5c..ed48b04ff 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -79,10 +79,14 @@ struct PaidMediaData { } } + const auto sender = item->originalSender(); + const auto broadcast = (sender && sender->isBroadcast()) + ? sender + : message->peer.get(); return { .invoice = invoice, .item = item, - .peer = message->peer, + .peer = broadcast, .photos = photos, .videos = videos, }; @@ -270,21 +274,11 @@ void SendCreditsBox( loadingAnimation->showOn(state->confirmButtonBusy.value()); } { - const auto emojiMargin = QMargins( - 0, - -st::moderateBoxExpandInnerSkip, - 0, - 0); - const auto buttonEmoji = Ui::Text::SingleCustomEmoji( - session->data().customEmojiManager().registerInternalEmoji( - st::settingsPremiumIconStar, - emojiMargin, - true)); auto buttonText = tr::lng_credits_box_out_confirm( lt_count, rpl::single(form->invoice.amount) | tr::to_count(), lt_emoji, - rpl::single(buttonEmoji), + rpl::single(CreditsEmoji(session)), Ui::Text::RichLangValue); const auto buttonLabel = Ui::CreateChild( button, @@ -359,4 +353,12 @@ void SendCreditsBox( } } +TextWithEntities CreditsEmoji(not_null session) { + return Ui::Text::SingleCustomEmoji( + session->data().customEmojiManager().registerInternalEmoji( + st::settingsPremiumIconStar, + QMargins{ 0, -st::moderateBoxExpandInnerSkip, 0, 0 }, + true)); +} + } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/send_credits_box.h b/Telegram/SourceFiles/boxes/send_credits_box.h index 25ceb1d56..71675c720 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.h +++ b/Telegram/SourceFiles/boxes/send_credits_box.h @@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class HistoryItem; +namespace Main { +class Session; +} // namespace Main + namespace Payments { struct CreditsFormData; } // namespace Payments @@ -22,4 +26,7 @@ void SendCreditsBox( std::shared_ptr data, Fn sent); +[[nodiscard]] TextWithEntities CreditsEmoji( + not_null session); + } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index ac27ddb0f..7cf1536c3 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -25,10 +25,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/controls/history_view_characters_limit.h" #include "history/view/history_view_schedule_box.h" #include "core/mime_type.h" +#include "core/ui_integration.h" #include "base/event_filter.h" #include "base/call_delayed.h" #include "boxes/premium_limits_box.h" #include "boxes/premium_preview_box.h" +#include "boxes/send_credits_box.h" #include "ui/effects/scroll_content_shadow.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/scroll_area.h" @@ -749,13 +751,26 @@ void SendFilesBox::refreshPriceTag() { p.drawRoundedRect(raw->rect(), radius, radius); }, raw->lifetime()); + const auto session = &_show->session(); auto price = _price.value() | rpl::map([=](uint64 amount) { - return QChar(0x2B50) + Lang::FormatCountDecimal(amount); + auto result = Ui::Text::Colorized(Ui::CreditsEmoji(session)); + result.append(Lang::FormatCountDecimal(amount)); + return result; }); + auto text = tr::lng_paid_price( + lt_price, + std::move(price), + Ui::Text::WithEntities); const auto label = Ui::CreateChild( raw, - tr::lng_paid_price(lt_price, std::move(price)), + QString(), st::paidTagLabel); + std::move(text) | rpl::start_with_next([=](TextWithEntities &&text) { + label->setMarkedText(text, Core::MarkedTextContext{ + .session = session, + .customEmojiRepaint = [=] { label->update(); }, + }); + }, label->lifetime()); label->show(); label->sizeValue() | rpl::start_with_next([=](QSize size) { const auto inner = QRect(QPoint(), size); @@ -1633,11 +1648,16 @@ void SendFilesBox::send( auto caption = (_caption && !_caption->isHidden()) ? _caption->getTextWithAppliedMarkdown() : TextWithTags(); - options.invertCaption = _invertCaption; - options.price = hasPrice() ? _price.current() : 0; if (!validateLength(caption.text)) { return; } + options.invertCaption = _invertCaption; + options.price = hasPrice() ? _price.current() : 0; + if (options.price > 0) { + for (auto &file : _list.files) { + file.spoiler = false; + } + } _confirmedCallback( std::move(_list), _sendWay.current(), diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index b268377f9..0b1290dc8 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1417,6 +1417,9 @@ paidAmountAbout: FlatLabel(defaultFlatLabel) { } paidTagLabel: FlatLabel(defaultFlatLabel) { textFg: radialFg; + palette: TextPalette(defaultTextPalette) { + linkFg: creditsBg1; + } style: semiboldTextStyle; } paidTagPadding: margins(16px, 6px, 16px, 6px); diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp index e5c01ba70..dd32fa00f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_views.h" #include "apiwrap.h" #include "ui/text/format_values.h" +#include "ui/text/text_utilities.h" #include "ui/painter.h" #include "core/click_handler_types.h" #include "data/data_document.h" @@ -25,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_theme_document.h" #include "history/history_item.h" #include "history/history.h" +#include "lang/lang_keys.h" #include "main/main_session.h" #include "mainwindow.h" #include "media/streaming/media_streaming_utility.h" @@ -34,6 +36,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_chat.h" namespace HistoryView { +namespace { + +constexpr auto kMediaUnlockedTooltipDuration = 5 * crl::time(1000); + +} // namespace void PaintInterpolatedIcon( QPainter &p, @@ -191,10 +198,36 @@ QSize CountPhotoMediaSize( media.scaled(media.width(), newWidth, Qt::KeepAspectRatio)); } +void ShowPaidMediaUnlockedToast( + not_null controller, + not_null item) { + const auto media = item->media(); + const auto invoice = media ? media->invoice() : nullptr; + if (!invoice || !invoice->isPaidMedia) { + return; + } + const auto sender = item->originalSender(); + const auto broadcast = (sender && sender->isBroadcast()) + ? sender + : item->history()->peer.get(); + auto text = tr::lng_credits_media_done_title( + tr::now, + Ui::Text::Bold + ).append('\n').append(tr::lng_credits_media_done_text( + tr::now, + lt_count, + invoice->amount, + lt_chat, + Ui::Text::Bold(broadcast->name()), + Ui::Text::RichLangValue)); + controller->showToast(std::move(text), kMediaUnlockedTooltipDuration); +} + ClickHandlerPtr MakePaidMediaLink(not_null item) { return std::make_shared([=](ClickContext context) { const auto my = context.other.value(); const auto controller = my.sessionWindow.get(); + const auto weak = my.sessionWindow; const auto itemId = item->fullId(); const auto session = &item->history()->session(); using Result = Payments::CheckoutResult; @@ -203,6 +236,9 @@ ClickHandlerPtr MakePaidMediaLink(not_null item) { return; } else if (const auto item = session->data().message(itemId)) { session->api().views().pollExtendedMedia(item, true); + if (const auto strong = weak.get()) { + ShowPaidMediaUnlockedToast(strong, item); + } } }); Payments::CheckoutProcess::Start( diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index e3f6c91c5..6d634073a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/media/history_view_photo.h" +#include "boxes/send_credits_box.h" #include "history/history_item_components.h" #include "history/history_item.h" #include "history/history.h" @@ -24,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image.h" #include "ui/effects/spoiler_mess.h" #include "ui/chat/chat_style.h" +#include "ui/text/text_utilities.h" #include "ui/grouped_layout.h" #include "ui/cached_round_corners.h" #include "ui/painter.h" @@ -38,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_auto_download.h" #include "data/data_web_page.h" #include "core/application.h" +#include "core/ui_integration.h" #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" @@ -65,6 +68,7 @@ struct Photo::PriceTag { QImage cache; QColor darken; QColor fg; + QColor star; ClickHandlerPtr link; }; @@ -438,9 +442,11 @@ void Photo::drawPriceTag( const auto st = context.st; const auto darken = st->msgDateImgBg()->c; const auto fg = st->msgDateImgFg()->c; + const auto star = st->creditsBg1()->c; if (_priceTag->cache.isNull() || _priceTag->darken != darken - || _priceTag->fg != fg) { + || _priceTag->fg != fg + || _priceTag->star != star) { auto bg = generateBackground(); if (bg.isNull()) { bg = QImage(2, 2, QImage::Format_ARGB32_Premultiplied); @@ -448,12 +454,21 @@ void Photo::drawPriceTag( } auto text = Ui::Text::String(); - text.setText( + const auto session = &history()->session(); + auto price = Ui::Text::Colorized(Ui::CreditsEmoji(session)); + price.append(Lang::FormatCountDecimal(_priceTag->price)); + text.setMarkedText( st::semiboldTextStyle, tr::lng_paid_price( tr::now, lt_price, - QChar(0x2B50) + Lang::FormatCountDecimal(_priceTag->price))); + price, + Ui::Text::WithEntities), + kMarkupTextOptions, + Core::MarkedTextContext{ + .session = session, + .customEmojiRepaint = [] {}, + }); const auto width = text.maxWidth(); const auto inner = QRect(0, 0, width, text.minHeight()); const auto outer = inner.marginsAdded(st::paidTagPadding); @@ -476,6 +491,7 @@ void Photo::drawPriceTag( bg); p.fillRect(QRect(QPoint(), size), darken); p.setPen(fg); + p.setTextPalette(st->priceTagTextPalette()); text.draw(p, -outer.x(), -outer.y(), width); p.end(); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index e4ff33434..a817f5dfe 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -106,6 +106,9 @@ outTextPaletteSelected: TextPalette(outTextPalette) { monoFg: msgOutMonoFgSelected; spoilerFg: msgOutDateFgSelected; } +priceTagTextPalette: TextPalette(defaultTextPalette) { + linkFg: creditsBg1; +} fwdTextUserpicPadding: margins(0px, 1px, 3px, 0px); fwdTextStyle: TextStyle(semiboldTextStyle) { linkUnderline: kLinkUnderlineNever; diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index 56f236481..0c50f77b8 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -171,6 +171,7 @@ ChatStyle::ChatStyle(rpl::producer colorIndices) { make(_historyPsaForwardPalette, st::historyPsaForwardPalette); make(_imgReplyTextPalette, st::imgReplyTextPalette); make(_serviceTextPalette, st::serviceTextPalette); + make(_priceTagTextPalette, st::priceTagTextPalette); make(_historyRepliesInvertedIcon, st::historyRepliesInvertedIcon); make(_historyViewsInvertedIcon, st::historyViewsInvertedIcon); make(_historyViewsSendingIcon, st::historyViewsSendingIcon); diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index f7499a2d0..a00fe941a 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -349,6 +349,9 @@ public: [[nodiscard]] const style::TextPalette &serviceTextPalette() const { return _serviceTextPalette; } + [[nodiscard]] const style::TextPalette &priceTagTextPalette() const { + return _priceTagTextPalette; + } [[nodiscard]] const style::icon &historyRepliesInvertedIcon() const { return _historyRepliesInvertedIcon; } @@ -516,6 +519,7 @@ private: style::TextPalette _historyPsaForwardPalette; style::TextPalette _imgReplyTextPalette; style::TextPalette _serviceTextPalette; + style::TextPalette _priceTagTextPalette; style::icon _historyRepliesInvertedIcon = { Qt::Uninitialized }; style::icon _historyViewsInvertedIcon = { Qt::Uninitialized }; style::icon _historyViewsSendingIcon = { Qt::Uninitialized }; From d47c5df73d1f0cc6cc8497c056251dbb7ef921c5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Jun 2024 12:41:27 +0400 Subject: [PATCH 081/142] Nice price tag on sending media. --- Telegram/SourceFiles/boxes/send_files_box.cpp | 58 +++++++++++++++++-- Telegram/SourceFiles/boxes/send_files_box.h | 4 ++ .../history/view/media/history_view_photo.cpp | 6 +- .../attach_abstract_single_media_preview.cpp | 4 ++ .../attach_abstract_single_media_preview.h | 2 + .../ui/chat/attach/attach_album_preview.cpp | 49 +++++++++++++++- .../ui/chat/attach/attach_album_preview.h | 6 +- .../ui/chat/attach/attach_album_thumbnail.cpp | 4 ++ .../ui/chat/attach/attach_album_thumbnail.h | 20 ++++--- 9 files changed, 132 insertions(+), 21 deletions(-) diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 7cf1536c3..754713062 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -398,6 +398,18 @@ void SendFilesBox::Block::applyChanges() { } } +QImage SendFilesBox::Block::generatePriceTagBackground() const { + const auto preview = _preview.get(); + if (_isAlbum) { + const auto album = static_cast(preview); + return album->generatePriceTagBackground(); + } else if (_isSingleMedia) { + const auto media = static_cast(preview); + return media->generatePriceTagBackground(); + } + return QImage(); +} + SendFilesBox::SendFilesBox( QWidget*, not_null controller, @@ -737,18 +749,17 @@ void SendFilesBox::refreshPriceTag() { } if (!hasPrice()) { _priceTag = nullptr; + _priceTagBg = QImage(); } else if (!_priceTag) { _priceTag = std::make_unique(_inner.data()); const auto raw = _priceTag.get(); raw->show(); raw->paintRequest() | rpl::start_with_next([=] { - auto p = QPainter(raw); - auto hq = PainterHighQualityEnabler(p); - p.setBrush(st::toastBg); - p.setPen(Qt::NoPen); - const auto radius = std::min(raw->width(), raw->height()) / 2.; - p.drawRoundedRect(raw->rect(), radius, radius); + if (_priceTagBg.isNull()) { + _priceTagBg = preparePriceTagBg(raw->size()); + } + QPainter(raw).drawImage(0, 0, _priceTagBg); }, raw->lifetime()); const auto session = &_show->session(); @@ -785,9 +796,44 @@ void SendFilesBox::refreshPriceTag() { }, raw->lifetime()); } else { _priceTag->raise(); + _priceTag->update(); + _priceTagBg = QImage(); } } +QImage SendFilesBox::preparePriceTagBg(QSize size) const { + const auto ratio = style::DevicePixelRatio(); + const auto outer = _blocks.empty() + ? size + : _inner->widgetAt(0)->geometry().size(); + auto bg = _blocks.empty() + ? QImage() + : _blocks.front().generatePriceTagBackground(); + if (bg.isNull()) { + bg = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied); + bg.fill(Qt::black); + } + const auto bgSize = bg.size() / bg.devicePixelRatio(); + + auto result = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(ratio); + result.fill(Qt::black); + auto p = QPainter(&result); + auto hq = PainterHighQualityEnabler(p); + p.drawImage( + QRect( + (size.width() - outer.width()) / 2, + (size.height() - outer.height()) / 2, + outer.width(), + outer.height()), + bg); + p.fillRect(QRect(QPoint(), size), st::msgDateImgBg); + p.end(); + + const auto radius = std::min(size.width(), size.height()) / 2; + return Images::Round(std::move(result), Images::CornersMask(radius)); +} + void SendFilesBox::addMenuButton() { const auto details = _sendMenuDetails(); if (!hasSendMenu(details)) { diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index f16dffb8f..7ba1a8070 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -166,6 +166,8 @@ private: void toggleSpoilers(bool enabled); void applyChanges(); + [[nodiscard]] QImage generatePriceTagBackground() const; + private: base::unique_qptr _preview; not_null*> _items; @@ -196,6 +198,7 @@ private: [[nodiscard]] bool canChangePrice() const; [[nodiscard]] bool hasPrice() const; void refreshPriceTag(); + [[nodiscard]] QImage preparePriceTagBg(QSize size) const; bool validateLength(const QString &text) const; void refreshButtons(); @@ -259,6 +262,7 @@ private: Fn _cancelledCallback; rpl::variable _price = 0; std::unique_ptr _priceTag; + QImage _priceTagBg; bool _confirmed = false; bool _invertCaption = false; diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 6d634073a..2485552d8 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -383,7 +383,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const { } } else if (preview) { drawPriceTag(p, rthumb, context, [&] { - return _spoiler ? _spoiler->background : QImage(); + return priceTagBackground(); }); } if (showEnlarge) { @@ -447,9 +447,10 @@ void Photo::drawPriceTag( || _priceTag->darken != darken || _priceTag->fg != fg || _priceTag->star != star) { + const auto ratio = style::DevicePixelRatio(); auto bg = generateBackground(); if (bg.isNull()) { - bg = QImage(2, 2, QImage::Format_ARGB32_Premultiplied); + bg = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied); bg.fill(Qt::black); } @@ -474,7 +475,6 @@ void Photo::drawPriceTag( const auto outer = inner.marginsAdded(st::paidTagPadding); const auto size = outer.size(); const auto radius = std::min(size.width(), size.height()) / 2; - const auto ratio = style::DevicePixelRatio(); auto cache = QImage( size * ratio, QImage::Format_ARGB32_Premultiplied); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp index b142a61c1..b1a211e49 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp @@ -84,6 +84,10 @@ rpl::producer AbstractSingleMediaPreview::spoileredChanges() const { return _spoileredChanges.events(); } +QImage AbstractSingleMediaPreview::generatePriceTagBackground() const { + return (_previewBlurred.isNull() ? _preview : _previewBlurred).toImage(); +} + void AbstractSingleMediaPreview::preparePreview(QImage preview) { auto maxW = 0; auto maxH = 0; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h index fd72facf1..ba9595f28 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h @@ -44,6 +44,8 @@ public: [[nodiscard]] bool canHaveSpoiler() const; [[nodiscard]] rpl::producer spoileredChanges() const; + [[nodiscard]] QImage generatePriceTagBackground() const; + protected: virtual bool supportsSpoilers() const = 0; virtual bool drawBackground() const = 0; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp index 76ff1ab2b..94d3b6286 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp @@ -20,6 +20,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include +namespace Media::Streaming { + +[[nodiscard]] QImage PrepareBlurredBackground(QSize outer, QImage frame); + +} // namespace Media::Streaming + namespace Ui { namespace { @@ -610,8 +616,47 @@ void AlbumPreview::switchToDrag() { update(); } -rpl::producer AlbumPreview::thumbModified() const { - return _thumbModified.events(); +QImage AlbumPreview::generatePriceTagBackground() const { + auto wmax = 0; + auto hmax = 0; + for (auto &thumb : _thumbs) { + const auto geometry = thumb->geometry(); + accumulate_max(wmax, geometry.x() + geometry.width()); + accumulate_max(hmax, geometry.y() + geometry.height()); + } + const auto size = QSize(wmax, hmax); + if (size.isEmpty()) { + return {}; + } + const auto ratio = style::DevicePixelRatio(); + const auto full = size * ratio; + const auto skip = st::historyGroupSkip; + auto result = QImage(full, QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(ratio); + result.fill(Qt::black); + auto p = QPainter(&result); + auto hq = PainterHighQualityEnabler(p); + for (auto &thumb : _thumbs) { + const auto geometry = thumb->geometry(); + if (geometry.isEmpty()) { + continue; + } + const auto w = geometry.width(); + const auto h = geometry.height(); + const auto wscale = (w + skip) / float64(w); + const auto hscale = (h + skip) / float64(h); + p.save(); + p.translate(geometry.center()); + p.scale(wscale, hscale); + p.translate(-geometry.center()); + thumb->paintInAlbum(p, 0, 0, 0., 0.); + p.restore(); + } + p.end(); + + return ::Media::Streaming::PrepareBlurredBackground( + full, + std::move(result)); } } // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h index f51eb26e9..9a4b8bc05 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h @@ -47,7 +47,11 @@ public: return _thumbChanged.events(); } - rpl::producer thumbModified() const; + [[nodiscard]] rpl::producer thumbModified() const { + return _thumbModified.events(); + } + + [[nodiscard]] QImage generatePriceTagBackground() const; protected: void paintEvent(QPaintEvent *e) override; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp index adf38109b..92025e3cc 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp @@ -503,6 +503,10 @@ void AlbumThumbnail::paintFile( _fileThumb.size() / style::DevicePixelRatio()); } +QRect AlbumThumbnail::geometry() const { + return _layout.geometry; +} + bool AlbumThumbnail::containsPoint(QPoint position) const { return _layout.geometry.contains(position); } diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.h b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.h index 1bfd8fc62..209f864cd 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.h @@ -42,8 +42,8 @@ public: void setSpoiler(bool spoiler); [[nodiscard]] bool hasSpoiler() const; - int photoHeight() const; - int fileHeight() const; + [[nodiscard]] int photoHeight() const; + [[nodiscard]] int fileHeight() const; void paintInAlbum( QPainter &p, @@ -54,20 +54,22 @@ public: void paintPhoto(Painter &p, int left, int top, int outerWidth); void paintFile(Painter &p, int left, int top, int outerWidth); - bool containsPoint(QPoint position) const; - bool buttonsContainPoint(QPoint position) const; - AttachButtonType buttonTypeFromPoint(QPoint position) const; - int distanceTo(QPoint position) const; - bool isPointAfter(QPoint position) const; + [[nodiscard]] QRect geometry() const; + [[nodiscard]] bool containsPoint(QPoint position) const; + [[nodiscard]] bool buttonsContainPoint(QPoint position) const; + [[nodiscard]] AttachButtonType buttonTypeFromPoint( + QPoint position) const; + [[nodiscard]] int distanceTo(QPoint position) const; + [[nodiscard]] bool isPointAfter(QPoint position) const; void moveInAlbum(QPoint to); - QPoint center() const; + [[nodiscard]] QPoint center() const; void suggestMove(float64 delta, Fn callback); void finishAnimations(); void setButtonVisible(bool value); void moveButtons(int thumbTop); - bool isCompressedSticker() const; + [[nodiscard]] bool isCompressedSticker() const; static constexpr auto kShrinkDuration = crl::time(150); From 968d03683414b62b569a239ce38358e2851d17d9 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Jun 2024 12:58:19 +0400 Subject: [PATCH 082/142] Use Ui::NumberInput for the paid media price. --- Telegram/SourceFiles/boxes/send_files_box.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 754713062..d1f5cfeb6 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/premium_preview_box.h" #include "boxes/send_credits_box.h" #include "ui/effects/scroll_content_shadow.h" +#include "ui/widgets/fields/number_input.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/popup_menu.h" @@ -124,13 +125,22 @@ void EditPriceBox( 0, st::defaultSubsectionTitlePadding.right(), 0))); - const auto field = box->addRow(object_ptr( + const auto wrap = box->addRow(object_ptr( box, + st::editTagField.heightMin)); + auto owned = object_ptr( + wrap, st::editTagField, tr::lng_paid_cost_placeholder(), - price ? QString::number(price) : QString())); + price ? QString::number(price) : QString(), + kMaxPrice); + const auto field = owned.data(); + wrap->widthValue() | rpl::start_with_next([=](int width) { + field->move(0, 0); + field->resize(width, field->height()); + wrap->resize(width, field->height()); + }, wrap->lifetime()); field->selectAll(); - field->setMaxLength(QString::number(kMaxPrice).size()); box->setFocusCallback([=] { field->setFocusFast(); }); @@ -168,8 +178,7 @@ void EditPriceBox( } }; - field->submits( - ) | rpl::start_with_next(save, field->lifetime()); + QObject::connect(field, &Ui::NumberInput::submitted, box, save); box->addButton(tr::lng_settings_save(), save); box->addButton(tr::lng_cancel(), [=] { From 5fdaa6b91fcc1b8ac203fa10d5498ad37dc3b7ee Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Jun 2024 13:33:15 +0400 Subject: [PATCH 083/142] Record bareMsgId in CreditsHistoryEntry. --- Telegram/SourceFiles/api/api_credits.cpp | 3 ++- Telegram/SourceFiles/boxes/gift_premium_box.cpp | 4 ++-- Telegram/SourceFiles/data/data_credits.h | 3 ++- .../info/statistics/info_statistics_list_controllers.cpp | 4 ++-- Telegram/SourceFiles/payments/payments_non_panel_process.cpp | 3 ++- Telegram/SourceFiles/settings/settings_credits_graphics.cpp | 4 ++-- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 4d296286b..fa6a0b8c5 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -43,7 +43,8 @@ constexpr auto kTransactionsLimit = 100; .date = base::unixtime::parse(tl.data().vdate().v), .photoId = photo ? photo->id : 0, .credits = tl.data().vstars().v, - .bareId = barePeerId, + .bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()), + .barePeerId = barePeerId, .peerType = tl.data().vpeer().match([](const HistoryPeerTL &) { return Data::CreditsHistoryEntry::PeerType::Peer; }, [](const MTPDstarsTransactionPeerPlayMarket &) { diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 150747663..44c5e3536 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -1646,12 +1646,12 @@ void AddCreditsHistoryEntryTable( container, st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); - if (entry.bareId) { + if (entry.barePeerId) { AddTableRow( table, tr::lng_credits_box_history_entry_peer(), controller, - PeerId(entry.bareId)); + PeerId(entry.barePeerId)); } using Type = Data::CreditsHistoryEntry::PeerType; if (entry.peerType == Type::AppStore) { diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index f013382e8..b1c9984f7 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -35,7 +35,8 @@ struct CreditsHistoryEntry final { QDateTime date; PhotoId photoId = 0; uint64 credits = 0; - uint64 bareId = 0; + uint64 bareMsgId = 0; + uint64 barePeerId = 0; PeerType peerType; bool refunded = false; bool pending = false; diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 92920d2fc..c9c103978 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -938,8 +938,8 @@ void CreditsController::applySlice(const Data::CreditsStatusSlice &slice) { }, }; using Type = Data::CreditsHistoryEntry::PeerType; - if (item.bareId) { - const auto peer = session().data().peer(PeerId(item.bareId)); + if (const auto peerId = PeerId(item.barePeerId)) { + const auto peer = session().data().peer(peerId); return std::make_unique(peer, descriptor); } else if (item.peerType == Type::PremiumBot) { return std::make_unique(_premiumBot, descriptor); diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp index aad25cdf5..69cf7abde 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -105,7 +105,8 @@ Fn ProcessNonPanelPaymentFormFactory( .date = base::unixtime::parse(receipt->date), .photoId = receipt->photo ? receipt->photo->id : 0, .credits = receipt->credits, - .bareId = receipt->peerId.value, + .bareMsgId = uint64(), + .barePeerId = receipt->peerId.value, .peerType = Data::CreditsHistoryEntry::PeerType::Peer, }; controller->uiShow()->show(Box( diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 63521ec31..0aa341dd2 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -336,8 +336,8 @@ void ReceiptCreditsBox( const auto &stUser = st::boostReplaceUserpic; const auto peer = (e.peerType == Type::PremiumBot) ? premiumBot - : e.bareId - ? controller->session().data().peer(PeerId(e.bareId)).get() + : e.barePeerId + ? controller->session().data().peer(PeerId(e.barePeerId)).get() : nullptr; const auto photo = e.photoId ? controller->session().data().photo(e.photoId).get() From 0dc92762bcc1b85b30ebad6723624808fd400ab8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Jun 2024 14:42:35 +0400 Subject: [PATCH 084/142] Show link to message in transactions history. --- Telegram/Resources/langs/lang.strings | 1 + .../SourceFiles/boxes/gift_premium_box.cpp | 35 +++++++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b8a0d6af7..cc6d7f51e 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2352,6 +2352,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_box_history_entry_fragment" = "Fragment"; "lng_credits_box_history_entry_id" = "Transaction ID"; "lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard."; +"lng_credits_box_history_entry_media" = "Media"; "lng_credits_box_history_entry_about" = "You can dispute this transaction {link}."; "lng_credits_box_history_entry_about_link" = "here"; "lng_credits_small_balance_title#one" = "{count} Star Needed"; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 44c5e3536..86d1ab4a7 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -1646,12 +1646,35 @@ void AddCreditsHistoryEntryTable( container, st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); - if (entry.barePeerId) { - AddTableRow( - table, - tr::lng_credits_box_history_entry_peer(), - controller, - PeerId(entry.barePeerId)); + const auto peerId = PeerId(entry.barePeerId); + if (peerId) { + auto text = tr::lng_credits_box_history_entry_peer(); + AddTableRow(table, std::move(text), controller, peerId); + } + if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) { + const auto session = &controller->session(); + const auto peer = session->data().peer(peerId); + if (const auto channel = peer->asBroadcast()) { + const auto username = channel->username(); + const auto base = username.isEmpty() + ? u"c/%1"_q.arg(peerToChannel(channel->id).bare) + : username; + const auto query = base + '/' + QString::number(msgId.bare); + const auto link = session->createInternalLink(query); + auto label = object_ptr( + table, + rpl::single(Ui::Text::Link(link)), + st::giveawayGiftCodeValue); + label->setClickHandlerFilter([=](const auto &...) { + controller->showPeerHistory(channel, {}, msgId); + return false; + }); + AddTableRow( + table, + tr::lng_credits_box_history_entry_media(), + std::move(label), + st::giveawayGiftCodeValueMargin); + } } using Type = Data::CreditsHistoryEntry::PeerType; if (entry.peerType == Type::AppStore) { From 101dbdf2437ce946d662ea39a3ed8bb3c0177d49 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 21 Jun 2024 15:17:55 +0300 Subject: [PATCH 085/142] Added support of MTP updates to lists of credits history entries. --- .../info/bot/earn/info_earn_inner_widget.cpp | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp index 026cacb08..6a1830142 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp @@ -555,23 +555,22 @@ void InnerWidget::fill() { void InnerWidget::fillHistory() { const auto container = this; + Ui::AddSkip(container, st::settingsPremiumOptionsPadding.top()); const auto history = container->add( - object_ptr>( - container, - object_ptr(container))); - const auto content = history->entity(); + object_ptr(container)); - Ui::AddSkip(content, st::settingsPremiumOptionsPadding.top()); + const auto sectionIndex = history->lifetime().make_state(0); const auto fill = [=]( not_null premiumBot, const Data::CreditsStatusSlice &fullSlice, const Data::CreditsStatusSlice &inSlice, const Data::CreditsStatusSlice &outSlice) { - const auto inner = content; if (fullSlice.list.empty()) { return; } + const auto inner = history->add( + object_ptr(history)); const auto hasOneTab = inSlice.list.empty() && outSlice.list.empty(); const auto hasIn = !inSlice.list.empty(); const auto hasOut = !outSlice.list.empty(); @@ -622,6 +621,8 @@ void InnerWidget::fillHistory() { slider->entity()->addSection(outTabText); } + slider->entity()->setActiveSectionFast(*sectionIndex); + { const auto &st = st::defaultTabsSlider; slider->entity()->setNaturalWidth(0 @@ -644,7 +645,7 @@ void InnerWidget::fillHistory() { inner, object_ptr(inner))); - rpl::single(0) | rpl::then( + rpl::single(slider->entity()->activeSection()) | rpl::then( slider->entity()->sectionActivated() ) | rpl::start_with_next([=](int index) { if (index == 0) { @@ -660,6 +661,7 @@ void InnerWidget::fillHistory() { fullWrap->toggle(false, anim::type::instant); inWrap->toggle(false, anim::type::instant); } + *sectionIndex = index; }, inner->lifetime()); const auto controller = _controller->parentController(); @@ -704,12 +706,12 @@ void InnerWidget::fillHistory() { Ui::AddSkip(inner); Ui::AddSkip(inner); - - inner->resizeToWidth(container->width()); }; - const auto apiLifetime = content->lifetime().make_state(); - { + const auto apiLifetime = history->lifetime().make_state(); + rpl::single(rpl::empty) | rpl::then( + _stateUpdated.events() + ) | rpl::start_with_next([=] { using Api = Api::CreditsHistory; const auto apiFull = apiLifetime->make_state(_peer, true, true); const auto apiIn = apiLifetime->make_state(_peer, true, false); @@ -721,12 +723,16 @@ void InnerWidget::fillHistory() { &_controller->session() ) | rpl::start_with_next([=](not_null bot) { fill(bot, fullSlice, inSlice, outSlice); + container->resizeToWidth(container->width()); + while (history->count() > 1) { + delete history->widgetAt(0); + } apiLifetime->destroy(); }, *apiLifetime); }); }); }); - } + }, history->lifetime()); } void InnerWidget::saveState(not_null memento) { From 21094fe11b788ee100882d689e91cd012aefbe0b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 21 Jun 2024 15:45:15 +0300 Subject: [PATCH 086/142] Slightly improved process of incoming credit history entries. --- Telegram/SourceFiles/api/api_credits.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index fa6a0b8c5..0ea3384da 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -36,6 +36,16 @@ constexpr auto kTransactionsLimit = 100; }, [](const auto &) { return PeerId(0); }).value; + const auto isBot = [&] { + if (barePeerId) { + if (const auto p = peer->owner().peer(PeerId(barePeerId))) { + if (const auto u = p->asUser()) { + return u->isBot(); + } + } + } + return false; + }(); return Data::CreditsHistoryEntry{ .id = qs(tl.data().vid()), .title = qs(tl.data().vtitle().value_or_empty()), @@ -65,7 +75,7 @@ constexpr auto kTransactionsLimit = 100; ? base::unixtime::parse(tl.data().vtransaction_date()->v) : QDateTime(), .finishUrl = qs(tl.data().vtransaction_url().value_or_empty()), - .in = (!barePeerId || tl.data().is_refund()) + .in = (!isBot || tl.data().is_refund()) && !tl.data().is_pending() && !tl.data().is_failed(), }; From cad25ae64473982d2de5415d5bd58c5b8b6a34ad Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 21 Jun 2024 15:58:42 +0300 Subject: [PATCH 087/142] Fixed unique row id in lists of credits history entries. --- .../statistics/info_statistics_list_controllers.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index c9c103978..eb3292987 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -45,6 +45,15 @@ using BoostCallback = Fn; constexpr auto kColorIndexUnclaimed = int(3); constexpr auto kColorIndexPending = int(4); +[[nodiscard]] PeerListRowId UniqueRowIdFromEntry( + const Data::CreditsHistoryEntry &entry) { + return UniqueRowIdFromString(entry.id + + (entry.refunded ? '1' : '0') + + (entry.pending ? '1' : '0') + + (entry.failed ? '1' : '0') + + (entry.in ? '1' : '0')); +} + void AddArrow(not_null parent) { const auto arrow = Ui::CreateChild(parent.get()); arrow->paintRequest( @@ -750,7 +759,7 @@ private: }; CreditsRow::CreditsRow(not_null peer, const Descriptor &descriptor) -: PeerListRow(peer, UniqueRowIdFromString(descriptor.entry.id)) +: PeerListRow(peer, UniqueRowIdFromEntry(descriptor.entry)) , _entry(descriptor.entry) , _creditIcon(descriptor.creditIcon) , _rowHeight(descriptor.rowHeight) { @@ -766,7 +775,7 @@ CreditsRow::CreditsRow(not_null peer, const Descriptor &descriptor) } CreditsRow::CreditsRow(const Descriptor &descriptor) -: PeerListRow(UniqueRowIdFromString(descriptor.entry.id)) +: PeerListRow(UniqueRowIdFromEntry(descriptor.entry)) , _entry(descriptor.entry) , _creditIcon(descriptor.creditIcon) , _rowHeight(descriptor.rowHeight) { From ffa4b1db874a2d5d7ae10d61f120077f8180b809 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 21 Jun 2024 16:56:53 +0300 Subject: [PATCH 088/142] Fixed withdrawal button for credits with minimal amount. --- Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp index 6a1830142..57fb6a910 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp @@ -488,7 +488,7 @@ void InnerWidget::fill() { .creditsAmount = [=, show = _controller->uiShow()] { const auto amount = input->getLastText().toULongLong(); const auto min = float64(WithdrawalMin(session)); - if (amount <= min) { + if (amount < min) { auto text = tr::lng_bot_earn_credits_out_minimal( tr::now, lt_link, From 2044f8f9ad88de00725e64ea64db2984e42a5d33 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 22 Jun 2024 09:33:19 +0400 Subject: [PATCH 089/142] Update API scheme on layer 183. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/api/api_credits.cpp | 2 ++ Telegram/SourceFiles/boxes/gift_premium_box.cpp | 5 +++++ Telegram/SourceFiles/data/data_credits.h | 1 + Telegram/SourceFiles/mtproto/scheme/api.tl | 4 ++++ 5 files changed, 13 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index cc6d7f51e..ff021318f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2350,6 +2350,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_box_history_entry_play_market" = "Play Market"; "lng_credits_box_history_entry_app_store" = "App Store"; "lng_credits_box_history_entry_fragment" = "Fragment"; +"lng_credits_box_history_entry_ads" = "Ads Platform"; "lng_credits_box_history_entry_id" = "Transaction ID"; "lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard."; "lng_credits_box_history_entry_media" = "Media"; diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 0ea3384da..860753e63 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -67,6 +67,8 @@ constexpr auto kTransactionsLimit = 100; return Data::CreditsHistoryEntry::PeerType::Unsupported; }, [](const MTPDstarsTransactionPeerPremiumBot &) { return Data::CreditsHistoryEntry::PeerType::PremiumBot; + }, [](const MTPDstarsTransactionPeerAds &) { + return Data::CreditsHistoryEntry::PeerType::Ads; }), .refunded = tl.data().is_refund(), .pending = tl.data().is_pending(), diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 86d1ab4a7..c7e22969f 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -1695,6 +1695,11 @@ void AddCreditsHistoryEntryTable( tr::lng_credits_box_history_entry_via(), tr::lng_credits_box_history_entry_fragment( Ui::Text::RichLangValue)); + } else if (entry.peerType == Type::Ads) { + AddTableRow( + table, + tr::lng_credits_box_history_entry_via(), + tr::lng_credits_box_history_entry_ads(Ui::Text::RichLangValue)); } if (!entry.id.isEmpty()) { constexpr auto kOneLineCount = 18; diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index b1c9984f7..9a12b3ef4 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -28,6 +28,7 @@ struct CreditsHistoryEntry final { Fragment, Unsupported, PremiumBot, + Ads, }; QString id; QString title; diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 9aa1c847d..f5e1ba2b3 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -1805,6 +1805,7 @@ starsTransactionPeerPlayMarket#7b560a0b = StarsTransactionPeer; starsTransactionPeerPremiumBot#250dbaf8 = StarsTransactionPeer; starsTransactionPeerFragment#e92fd902 = StarsTransactionPeer; starsTransactionPeer#d80da15d peer:Peer = StarsTransactionPeer; +starsTransactionPeerAds#60682812 = StarsTransactionPeer; starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; @@ -1824,6 +1825,8 @@ payments.starsRevenueStats#c92bb73b revenue_graph:StatsGraph status:StarsRevenue payments.starsRevenueWithdrawalUrl#1dab80b7 url:string = payments.StarsRevenueWithdrawalUrl; +payments.starsRevenueAdsAccountUrl#394e7f21 url:string = payments.StarsRevenueAdsAccountUrl; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -2371,6 +2374,7 @@ payments.sendStarsForm#2bb731d flags:# form_id:long invoice:InputInvoice = payme 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; +payments.getStarsRevenueAdsAccountUrl#d1d7efc5 peer:InputPeer = payments.StarsRevenueAdsAccountUrl; 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; From 27a71a8dcdef8351bd1611db1f1e244f6d47cb30 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 22 Jun 2024 04:56:10 +0300 Subject: [PATCH 090/142] Renamed BotEarnStatistics to CreditsEarnStatistics. --- Telegram/SourceFiles/api/api_credits.cpp | 14 ++++++++------ Telegram/SourceFiles/api/api_credits.h | 9 +++++---- Telegram/SourceFiles/data/data_bot_earn.h | 10 +++++----- .../info/bot/earn/info_earn_inner_widget.cpp | 8 ++++---- .../info/bot/earn/info_earn_inner_widget.h | 2 +- .../SourceFiles/info/bot/earn/info_earn_widget.h | 2 +- 6 files changed, 24 insertions(+), 21 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 860753e63..50544f77e 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_updates.h" #include "apiwrap.h" #include "base/unixtime.h" +#include "data/data_channel.h" #include "data/data_peer.h" #include "data/data_photo.h" #include "data/data_session.h" @@ -227,21 +228,22 @@ rpl::producer> PremiumPeerBot( }; } -BotEarnStatistics::BotEarnStatistics(not_null user) -: StatisticsRequestSender(user) { +CreditsEarnStatistics::CreditsEarnStatistics(not_null peer) +: StatisticsRequestSender(peer) +, _isUser(peer->isUser()) { } -rpl::producer BotEarnStatistics::request() { +rpl::producer CreditsEarnStatistics::request() { return [=](auto consumer) { auto lifetime = rpl::lifetime(); makeRequest(MTPpayments_GetStarsRevenueStats( MTP_flags(0), - user()->input + (_isUser ? user()->input : channel()->input) )).done([=](const MTPpayments_StarsRevenueStats &result) { const auto &data = result.data(); const auto &status = data.vstatus().data(); - _data = Data::BotEarnStatistics{ + _data = Data::CreditsEarnStatistics{ .revenueGraph = StatisticalGraphFromTL(data.vrevenue_graph()), .currentBalance = status.vcurrent_balance().v, .availableBalance = status.vavailable_balance().v, @@ -262,7 +264,7 @@ rpl::producer BotEarnStatistics::request() { }; } -Data::BotEarnStatistics BotEarnStatistics::data() const { +Data::CreditsEarnStatistics CreditsEarnStatistics::data() const { return _data; } diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h index ae9ff5115..ae2e0984d 100644 --- a/Telegram/SourceFiles/api/api_credits.h +++ b/Telegram/SourceFiles/api/api_credits.h @@ -72,15 +72,16 @@ private: }; -class BotEarnStatistics final : public StatisticsRequestSender { +class CreditsEarnStatistics final : public StatisticsRequestSender { public: - explicit BotEarnStatistics(not_null); + explicit CreditsEarnStatistics(not_null); [[nodiscard]] rpl::producer request(); - [[nodiscard]] Data::BotEarnStatistics data() const; + [[nodiscard]] Data::CreditsEarnStatistics data() const; private: - Data::BotEarnStatistics _data; + Data::CreditsEarnStatistics _data; + bool _isUser = false; mtpRequestId _requestId = 0; diff --git a/Telegram/SourceFiles/data/data_bot_earn.h b/Telegram/SourceFiles/data/data_bot_earn.h index e208ccf00..080feb174 100644 --- a/Telegram/SourceFiles/data/data_bot_earn.h +++ b/Telegram/SourceFiles/data/data_bot_earn.h @@ -13,16 +13,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { -using BotEarnInt = uint64; +using CreditsEarnInt = uint64; -struct BotEarnStatistics final { +struct CreditsEarnStatistics final { explicit operator bool() const { return !!usdRate; } Data::StatisticalGraph revenueGraph; - BotEarnInt currentBalance = 0; - BotEarnInt availableBalance = 0; - BotEarnInt overallRevenue = 0; + CreditsEarnInt currentBalance = 0; + CreditsEarnInt availableBalance = 0; + CreditsEarnInt overallRevenue = 0; float64 usdRate = 0.; bool isWithdrawalEnabled = false; QDateTime nextWithdrawalAt; diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp index 57fb6a910..67ee8075d 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp @@ -99,8 +99,8 @@ InnerWidget::InnerWidget( void InnerWidget::load() { const auto apiLifetime = lifetime().make_state(); - const auto request = [=](Fn done) { - const auto api = apiLifetime->make_state( + const auto request = [=](Fn done) { + const auto api = apiLifetime->make_state( _peer->asUser()); api->request( ) | rpl::start_with_error_done([show = _show](const QString &error) { @@ -118,7 +118,7 @@ void InnerWidget::load() { _showFinished.events( ) | rpl::take(1) | rpl::start_with_next([=] { - request([=](Data::BotEarnStatistics state) { + request([=](Data::CreditsEarnStatistics state) { _state = state; _loaded.fire(true); fill(); @@ -129,7 +129,7 @@ void InnerWidget::load() { Api::PerformForUpdate(updates, [&](const TL &d) { const auto peerId = peerFromMTP(d.vpeer()); if (peerId == _peer->id) { - request([=](Data::BotEarnStatistics state) { + request([=](Data::CreditsEarnStatistics state) { _state = state; _stateUpdated.fire({}); }); diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h index 65558b3e2..60207334d 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h @@ -57,7 +57,7 @@ private: not_null _peer; std::shared_ptr _show; - Data::BotEarnStatistics _state; + Data::CreditsEarnStatistics _state; rpl::event_stream _scrollToRequests; rpl::event_stream _showRequests; diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_widget.h b/Telegram/SourceFiles/info/bot/earn/info_earn_widget.h index b201a32af..12397750e 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_widget.h +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_widget.h @@ -27,7 +27,7 @@ public: Section section() const override; - using SavedState = Data::BotEarnStatistics; + using SavedState = Data::CreditsEarnStatistics; void setState(SavedState states); [[nodiscard]] SavedState state(); From b15f5f859685674139d90861b8d075ad98df3b7a Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 22 Jun 2024 05:06:28 +0300 Subject: [PATCH 091/142] Added credits data to saved state of channel earn section. --- .../earn/info_earn_inner_widget.cpp | 25 +++++++++++++------ .../earn/info_earn_inner_widget.h | 4 +-- .../earn/info_earn_widget.h | 6 ++++- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp index 4516135ae..ffa2c403d 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/channel_statistics/earn/info_earn_inner_widget.h" +#include "api/api_credits.h" #include "api/api_earn.h" #include "api/api_statistics.h" #include "base/unixtime.h" @@ -258,27 +259,37 @@ InnerWidget::InnerWidget( void InnerWidget::load() { const auto api = lifetime().make_state( _peer->asChannel()); + const auto apiCredits = lifetime().make_state( + _peer); Info::Statistics::FillLoading( this, _loaded.events_starting_with(false) | rpl::map(!rpl::mappers::_1), _showFinished.events()); + const auto fail = [show = _controller->uiShow()](const QString &error) { + show->showToast(error); + }; + _showFinished.events( ) | rpl::take(1) | rpl::start_with_next([=] { api->request( - ) | rpl::start_with_error_done([](const QString &error) { - }, [=] { - _state = api->data(); - _loaded.fire(true); - fill(); + ) | rpl::start_with_error_done(fail, [=] { + _state.currencyEarn = api->data(); + apiCredits->request( + ) | rpl::start_with_error_done(fail, [=] { + _state.creditsEarn = apiCredits->data(); + _loaded.fire(true); + fill(); + }, lifetime()); }, lifetime()); }, lifetime()); } void InnerWidget::fill() { const auto container = this; - const auto &data = _state; + const auto &data = _state.currencyEarn; + const auto &creditsData = _state.creditsEarn; constexpr auto kMinus = QChar(0x2212); //constexpr auto kApproximately = QChar(0x2248); @@ -1106,7 +1117,7 @@ void InnerWidget::saveState(not_null memento) { void InnerWidget::restoreState(not_null memento) { _state = memento->state(); - if (_state) { + if (_state.currencyEarn || _state.creditsEarn) { fill(); } else { load(); diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.h b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.h index 99e9511be..6034a231a 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.h +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.h @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "data/data_channel_earn.h" +#include "info/channel_statistics/earn/info_earn_widget.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/vertical_layout.h" @@ -56,7 +56,7 @@ private: not_null _peer; std::shared_ptr _show; - Data::EarnStatistics _state; + Memento::SavedState _state; rpl::event_stream _scrollToRequests; rpl::event_stream _showRequests; diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_widget.h b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_widget.h index 73b373769..40e0f3f2c 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_widget.h +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_widget.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "data/data_channel_earn.h" +#include "data/data_bot_earn.h" #include "info/info_content_widget.h" namespace Info::ChannelEarn { @@ -27,7 +28,10 @@ public: Section section() const override; - using SavedState = Data::EarnStatistics; + struct SavedState final { + Data::EarnStatistics currencyEarn; + Data::CreditsEarnStatistics creditsEarn; + }; void setState(SavedState states); [[nodiscard]] SavedState state(); From 68bf6f991c9b0a4619446b2633dd080437748d48 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 22 Jun 2024 05:46:39 +0300 Subject: [PATCH 092/142] Moved out util for wrapping credits icon to widget. --- .../info/bot/earn/info_earn_inner_widget.cpp | 26 +++++-------------- .../ui/effects/credits_graphics.cpp | 15 +++++++++++ .../SourceFiles/ui/effects/credits_graphics.h | 8 ++++++ 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp index 67ee8075d..5605dfce2 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp @@ -70,20 +70,6 @@ void AddHeader( header->resizeToWidth(header->width()); } -[[nodiscard]] not_null CreateIconWidget( - not_null parent, - QImage image) { - const auto widget = Ui::CreateChild(parent); - widget->resize(image.size() / style::DevicePixelRatio()); - widget->paintRequest( - ) | rpl::start_with_next([=] { - auto p = QPainter(widget); - p.drawImage(0, 0, image); - }, widget->lifetime()); - widget->setAttribute(Qt::WA_TransparentForMouseEvents); - return widget; -} - } // namespace InnerWidget::InnerWidget( @@ -188,9 +174,9 @@ void InnerWidget::fill() { line, rpl::duplicate(value) | rpl::map(valueToString), st::channelEarnOverviewMajorLabel); - const auto icon = CreateIconWidget( + const auto icon = Ui::CreateSingleStarWidget( line, - Ui::GenerateStars(majorLabel->height(), 1)); + majorLabel->height()); const auto secondMinorLabel = Ui::CreateChild( line, std::move( @@ -260,9 +246,9 @@ void InnerWidget::fill() { labels, rpl::duplicate(availableBalanceValue) | rpl::map(valueToString), st::channelEarnBalanceMajorLabel); - const auto icon = CreateIconWidget( + const auto icon = Ui::CreateSingleStarWidget( labels, - Ui::GenerateStars(majorLabel->height(), 1)); + majorLabel->height()); majorLabel->setAttribute(Qt::WA_TransparentForMouseEvents); majorLabel->sizeValue( ) | rpl::start_with_next([=](const QSize &majorSize) { @@ -307,9 +293,9 @@ void InnerWidget::fill() { input->changeLimit(v); input->setText(QString::number(v)); }, input->lifetime()); - const auto icon = CreateIconWidget( + const auto icon = Ui::CreateSingleStarWidget( inputContainer, - Ui::GenerateStars(st.style.font->height, 1)); + st.style.font->height); inputContainer->sizeValue( ) | rpl::start_with_next([=](const QSize &size) { input->resize( diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index ce257b936..23b7503ea 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -90,6 +90,21 @@ QImage GenerateStars(int height, int count) { return frame; } +not_null CreateSingleStarWidget( + not_null parent, + int height) { + const auto widget = Ui::CreateChild(parent); + const auto image = GenerateStars(height, 1); + widget->resize(image.size() / style::DevicePixelRatio()); + widget->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(widget); + p.drawImage(0, 0, image); + }, widget->lifetime()); + widget->setAttribute(Qt::WA_TransparentForMouseEvents); + return widget; +} + PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( const Data::CreditsHistoryEntry &entry) { const auto bg = [&]() -> Ui::EmptyUserpic::BgColors { diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.h b/Telegram/SourceFiles/ui/effects/credits_graphics.h index a730cffec..eacc9148b 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.h +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.h @@ -13,10 +13,18 @@ namespace Data { struct CreditsHistoryEntry; } // namespace Data +namespace Ui { +class RpWidget; +} // namespace Ui + namespace Ui { [[nodiscard]] QImage GenerateStars(int height, int count); +[[nodiscard]] not_null CreateSingleStarWidget( + not_null parent, + int height); + Fn GenerateCreditsPaintUserpicCallback( const Data::CreditsHistoryEntry &entry); From c6e322de8624aba8519a5032026d2e6e1d8c29d8 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 22 Jun 2024 05:47:38 +0300 Subject: [PATCH 093/142] Added credits oveview to section of channel earn. --- .../earn/info_earn_inner_widget.cpp | 72 ++++++++++++++++--- 1 file changed, 61 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp index ffa2c403d..2176492d5 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp @@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/boost_box.h" #include "ui/controls/userpic_button.h" #include "ui/effects/animation_value_f.h" +#include "ui/effects/credits_graphics.h" #include "ui/effects/toggle_arrow.h" #include "ui/layers/generic_box.h" #include "ui/painter.h" @@ -277,7 +278,11 @@ void InnerWidget::load() { ) | rpl::start_with_error_done(fail, [=] { _state.currencyEarn = api->data(); apiCredits->request( - ) | rpl::start_with_error_done(fail, [=] { + ) | rpl::start_with_error_done([=](const QString &error) { + fail(error); + _loaded.fire(true); + fill(); + }, [=] { _state.creditsEarn = apiCredits->data(); _loaded.fire(true); fill(); @@ -576,6 +581,7 @@ void InnerWidget::fill() { const auto addOverview = [&]( EarnInt value, + EarnInt credits, const tr::phrase<> &text) { const auto line = container->add( Ui::CreateSkipWidget(container, 0), @@ -592,21 +598,56 @@ void InnerWidget::fill() { line, value ? ToUsd(value, multiplier) : QString(), st::channelEarnOverviewSubMinorLabel); + + const auto creditsLabel = Ui::CreateChild( + line, + QString::number(credits), + st::channelEarnOverviewMajorLabel); + const auto icon = Ui::CreateSingleStarWidget( + line, + creditsLabel->height()); + const auto creditsMultiplies = creditsData.usdRate + * Data::kEarnMultiplier; + const auto creditsSecondLabel = Ui::CreateChild( + line, + credits ? ToUsd(credits, creditsMultiplies) : QString(), + st::channelEarnOverviewSubMinorLabel); rpl::combine( line->widthValue(), - majorLabel->sizeValue() - ) | rpl::start_with_next([=](int available, const QSize &size) { + majorLabel->sizeValue(), + creditsLabel->sizeValue() + ) | rpl::start_with_next([=]( + int available, + const QSize &size, + const QSize &creditsSize) { + const auto skip = st::channelEarnOverviewSubMinorLabelPos.x(); line->resize(line->width(), size.height()); minorLabel->moveToLeft( size.width(), st::channelEarnOverviewMinorLabelSkip); - secondMinorLabel->resizeToWidth(available - - size.width() - - minorLabel->width()); + secondMinorLabel->resizeToWidth( + (credits ? (available / 2) : available) + - size.width() + - minorLabel->width()); secondMinorLabel->moveToLeft( - rect::right(minorLabel) - + st::channelEarnOverviewSubMinorLabelPos.x(), + rect::right(minorLabel) + skip, st::channelEarnOverviewSubMinorLabelPos.y()); + + icon->moveToLeft( + available / 2 + st::boxRowPadding.left() / 2, + 0); + creditsLabel->moveToLeft(rect::right(icon) + skip, 0); + creditsSecondLabel->moveToLeft( + rect::right(creditsLabel) + skip, + st::channelEarnOverviewSubMinorLabelPos.y()); + creditsSecondLabel->resizeToWidth( + available - creditsSecondLabel->pos().x()); + if (!credits) { + const auto x = std::numeric_limits::max(); + icon->moveToLeft(x, 0); + creditsLabel->moveToLeft(x, 0); + creditsSecondLabel->moveToLeft(x, 0); + } }, minorLabel->lifetime()); Ui::ToggleChildrenVisibility(line, true); @@ -619,13 +660,22 @@ void InnerWidget::fill() { st::boxRowPadding); sub->setTextColorOverride(st::windowSubTextFg->c); }; - addOverview(data.availableBalance, tr::lng_channel_earn_available); + addOverview( + data.availableBalance, + creditsData.availableBalance, + tr::lng_channel_earn_available); Ui::AddSkip(container); Ui::AddSkip(container); - addOverview(data.currentBalance, tr::lng_channel_earn_reward); + addOverview( + data.currentBalance, + creditsData.currentBalance, + tr::lng_channel_earn_reward); Ui::AddSkip(container); Ui::AddSkip(container); - addOverview(data.overallRevenue, tr::lng_channel_earn_total); + addOverview( + data.overallRevenue, + creditsData.overallRevenue, + tr::lng_channel_earn_total); Ui::AddSkip(container); } #ifndef _DEBUG From ae1f36473041ba922446039ac9b9f9c816dbbd19 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 22 Jun 2024 06:27:44 +0300 Subject: [PATCH 094/142] Added rpl support to label with icon in section of channel earn. --- .../earn/info_earn_inner_widget.cpp | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp index 2176492d5..b271eac58 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp @@ -315,7 +315,7 @@ void InnerWidget::fill() { }; const auto addEmojiToMajor = [=]( not_null label, - EarnInt value, + rpl::producer value, std::optional isIn, std::optional margins) { const auto &st = label->st(); @@ -330,12 +330,16 @@ void InnerWidget::fill() { : st::menuIconAttentionColor->c), margins ? *margins : st::channelEarnCurrencyCommonMargins, false)); - auto prepended = !isIn + const auto prepended = !isIn ? TextWithEntities() : TextWithEntities::Simple((*isIn) ? QChar('+') : kMinus); - label->setMarkedText( - prepended.append(icon).append(MajorPart(value)), - makeContext(label)); + std::move( + value + ) | rpl::start_with_next([=](EarnInt v) { + label->setMarkedText( + base::duplicate(prepended).append(icon).append(MajorPart(v)), + makeContext(label)); + }, label->lifetime()); }; const auto bigCurrencyIcon = Ui::Text::SingleCustomEmoji( @@ -589,7 +593,7 @@ void InnerWidget::fill() { const auto majorLabel = Ui::CreateChild( line, st::channelEarnOverviewMajorLabel); - addEmojiToMajor(majorLabel, value, {}, {}); + addEmojiToMajor(majorLabel, rpl::single(value), {}, {}); const auto minorLabel = Ui::CreateChild( line, MinorPart(value), @@ -704,7 +708,7 @@ void InnerWidget::fill() { { const auto &m = st::channelEarnCurrencyCommonMargins; const auto p = QMargins(m.left(), 0, m.right(), m.bottom()); - addEmojiToMajor(majorLabel, value, {}, p); + addEmojiToMajor(majorLabel, rpl::single(value), {}, p); } majorLabel->setAttribute(Qt::WA_TransparentForMouseEvents); const auto minorLabel = Ui::CreateChild( @@ -855,7 +859,7 @@ void InnerWidget::fill() { const auto majorLabel = Ui::CreateChild( wrap, st::channelEarnHistoryMajorLabel); - addEmojiToMajor(majorLabel, entry.amount, isIn, {}); + addEmojiToMajor(majorLabel, rpl::single(entry.amount), isIn, {}); majorLabel->setAttribute(Qt::WA_TransparentForMouseEvents); majorLabel->setTextColorOverride(color); const auto minorText = MinorPart(entry.amount); @@ -885,7 +889,7 @@ void InnerWidget::fill() { const auto majorLabel = Ui::CreateChild( labels, st::channelEarnOverviewMajorLabel); - addEmojiToMajor(majorLabel, amount, isIn, {}); + addEmojiToMajor(majorLabel, rpl::single(amount), isIn, {}); majorLabel->setAttribute(Qt::WA_TransparentForMouseEvents); majorLabel->setTextColorOverride(color); const auto minorLabel = Ui::CreateChild( From 63c4c5064fb39ce989bf0941423cfc3fcbadb2bd Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 22 Jun 2024 06:28:44 +0300 Subject: [PATCH 095/142] Added support of api updates for overview in channel earn section. --- .../earn/info_earn_inner_widget.cpp | 130 ++++++++++++++---- .../earn/info_earn_inner_widget.h | 1 + 2 files changed, 104 insertions(+), 27 deletions(-) diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp index b271eac58..0e97214e0 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_credits.h" #include "api/api_earn.h" +#include "api/api_filter_updates.h" #include "api/api_statistics.h" #include "base/unixtime.h" #include "boxes/peers/edit_peer_color_box.h" // AddLevelBadge. @@ -27,11 +28,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/statistics/info_statistics_inner_widget.h" // FillLoading. #include "iv/iv_instance.h" #include "lang/lang_keys.h" +#include "main/main_account.h" #include "main/main_app_config.h" #include "main/main_session.h" #include "statistics/chart_widget.h" #include "ui/basic_click_handlers.h" -#include "ui/widgets/label_with_custom_emoji.h" #include "ui/boxes/boost_box.h" #include "ui/controls/userpic_button.h" #include "ui/effects/animation_value_f.h" @@ -43,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/vertical_list.h" #include "ui/widgets/fields/input_field.h" +#include "ui/widgets/label_with_custom_emoji.h" #include "ui/widgets/popup_menu.h" #include "ui/wrap/slide_wrap.h" #include "styles/style_boxes.h" @@ -258,6 +260,17 @@ InnerWidget::InnerWidget( } void InnerWidget::load() { + struct State final { + State(not_null peer) + : api(peer->asChannel()) + , apiCredits(peer) { + } + Api::ChannelEarnStatistics api; + Api::CreditsEarnStatistics apiCredits; + rpl::lifetime apiLifetime; + rpl::lifetime apiCreditsLifetime; + }; + const auto state = lifetime().make_state(_peer); const auto api = lifetime().make_state( _peer->asChannel()); const auto apiCredits = lifetime().make_state( @@ -272,22 +285,56 @@ void InnerWidget::load() { show->showToast(error); }; + const auto finish = [=] { + _loaded.fire(true); + fill(); + state->apiLifetime.destroy(); + state->apiCreditsLifetime.destroy(); + + _peer->session().account().mtpUpdates( + ) | rpl::start_with_next([=, peerId = _peer->id]( + const MTPUpdates &updates) { + using TLCreditsUpdate = MTPDupdateStarsRevenueStatus; + using TLCurrencyUpdate = MTPDupdateBroadcastRevenueTransactions; + Api::PerformForUpdate(updates, [&]( + const TLCreditsUpdate &d) { + if (peerId == peerFromMTP(d.vpeer())) { + apiCredits->request( + ) | rpl::start_with_error_done(fail, [=] { + state->apiCreditsLifetime.destroy(); + _state.creditsEarn = state->apiCredits.data(); + _stateUpdated.fire({}); + }, state->apiCreditsLifetime); + } + }); + Api::PerformForUpdate(updates, [&]( + const TLCurrencyUpdate &d) { + if (peerId == peerFromMTP(d.vpeer())) { + const auto &data = d.vbalances().data(); + auto &e = _state.currencyEarn; + e.currentBalance = data.vcurrent_balance().v; + e.availableBalance = data.vavailable_balance().v; + e.overallRevenue = data.voverall_revenue().v; + _stateUpdated.fire({}); + } + }); + }, lifetime()); + }; + _showFinished.events( ) | rpl::take(1) | rpl::start_with_next([=] { - api->request( + state->api.request( ) | rpl::start_with_error_done(fail, [=] { - _state.currencyEarn = api->data(); - apiCredits->request( + _state.currencyEarn = state->api.data(); + state->apiCredits.request( ) | rpl::start_with_error_done([=](const QString &error) { fail(error); - _loaded.fire(true); - fill(); + finish(); }, [=] { - _state.creditsEarn = apiCredits->data(); - _loaded.fire(true); - fill(); - }, lifetime()); - }, lifetime()); + _state.creditsEarn = state->apiCredits.data(); + finish(); + }, state->apiCreditsLifetime); + }, state->apiLifetime); }, lifetime()); } @@ -296,6 +343,20 @@ void InnerWidget::fill() { const auto &data = _state.currencyEarn; const auto &creditsData = _state.creditsEarn; + auto currencyStateValue = rpl::single( + data + ) | rpl::then( + _stateUpdated.events() | rpl::map([=] { + return _state.currencyEarn; + }) + ); + + auto creditsStateValue = rpl::single( + creditsData + ) | rpl::then( + _stateUpdated.events() | rpl::map([=] { return _state.creditsEarn; }) + ); + constexpr auto kMinus = QChar(0x2212); //constexpr auto kApproximately = QChar(0x2248); const auto multiplier = data.usdRate; @@ -584,8 +645,8 @@ void InnerWidget::fill() { Ui::AddSkip(container, st::channelEarnOverviewTitleSkip); const auto addOverview = [&]( - EarnInt value, - EarnInt credits, + rpl::producer currencyValue, + rpl::producer creditsValue, const tr::phrase<> &text) { const auto line = container->add( Ui::CreateSkipWidget(container, 0), @@ -593,37 +654,49 @@ void InnerWidget::fill() { const auto majorLabel = Ui::CreateChild( line, st::channelEarnOverviewMajorLabel); - addEmojiToMajor(majorLabel, rpl::single(value), {}, {}); + addEmojiToMajor(majorLabel, rpl::duplicate(currencyValue), {}, {}); const auto minorLabel = Ui::CreateChild( line, - MinorPart(value), + rpl::duplicate(currencyValue) | rpl::map(MinorPart), st::channelEarnOverviewMinorLabel); const auto secondMinorLabel = Ui::CreateChild( line, - value ? ToUsd(value, multiplier) : QString(), + std::move( + currencyValue + ) | rpl::map([=](EarnInt value) { + return value ? ToUsd(value, multiplier) : QString(); + }), st::channelEarnOverviewSubMinorLabel); const auto creditsLabel = Ui::CreateChild( line, - QString::number(credits), + rpl::duplicate(creditsValue) | rpl::map([](EarnInt value) { + return QString::number(value); + }), st::channelEarnOverviewMajorLabel); const auto icon = Ui::CreateSingleStarWidget( line, creditsLabel->height()); - const auto creditsMultiplies = creditsData.usdRate + const auto creditsMultiplier = creditsData.usdRate * Data::kEarnMultiplier; const auto creditsSecondLabel = Ui::CreateChild( line, - credits ? ToUsd(credits, creditsMultiplies) : QString(), + rpl::duplicate( + creditsValue + ) | rpl::map([creditsMultiplier](EarnInt c) { + return c ? ToUsd(c, creditsMultiplier) : QString(); + }), st::channelEarnOverviewSubMinorLabel); rpl::combine( line->widthValue(), majorLabel->sizeValue(), - creditsLabel->sizeValue() + creditsLabel->sizeValue(), + std::move(creditsValue) ) | rpl::start_with_next([=]( int available, const QSize &size, - const QSize &creditsSize) { + const QSize &creditsSize, + EarnInt credits) { const auto skip = st::channelEarnOverviewSubMinorLabelPos.x(); line->resize(line->width(), size.height()); minorLabel->moveToLeft( @@ -664,21 +737,24 @@ void InnerWidget::fill() { st::boxRowPadding); sub->setTextColorOverride(st::windowSubTextFg->c); }; + auto availValueMap = [](const auto &v) { return v.availableBalance; }; + auto currentValueMap = [](const auto &v) { return v.currentBalance; }; + auto overallValueMap = [](const auto &v) { return v.overallRevenue; }; addOverview( - data.availableBalance, - creditsData.availableBalance, + rpl::duplicate(currencyStateValue) | rpl::map(availValueMap), + rpl::duplicate(creditsStateValue) | rpl::map(availValueMap), tr::lng_channel_earn_available); Ui::AddSkip(container); Ui::AddSkip(container); addOverview( - data.currentBalance, - creditsData.currentBalance, + rpl::duplicate(currencyStateValue) | rpl::map(currentValueMap), + rpl::duplicate(creditsStateValue) | rpl::map(currentValueMap), tr::lng_channel_earn_reward); Ui::AddSkip(container); Ui::AddSkip(container); addOverview( - data.overallRevenue, - creditsData.overallRevenue, + rpl::duplicate(currencyStateValue) | rpl::map(overallValueMap), + rpl::duplicate(creditsStateValue) | rpl::map(overallValueMap), tr::lng_channel_earn_total); Ui::AddSkip(container); } diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.h b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.h index 6034a231a..d9f43192e 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.h +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.h @@ -63,6 +63,7 @@ private: rpl::event_stream<> _showFinished; rpl::event_stream<> _focusRequested; rpl::event_stream _loaded; + rpl::event_stream<> _stateUpdated; }; From f4674389d5fb72be5ffdc3687ca537459fb5e934 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 22 Jun 2024 06:54:35 +0300 Subject: [PATCH 096/142] Added chart of credits revenue to section of channel earn. --- .../earn/info_earn_inner_widget.cpp | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp index 0e97214e0..018ecb00a 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp @@ -610,6 +610,7 @@ void InnerWidget::fill() { using Type = Statistic::ChartViewType; Ui::AddSkip(container); Ui::AddSkip(container); + auto hasPreviousChart = false; if (data.topHoursGraph.chart) { const auto widget = container->add( object_ptr(container), @@ -617,24 +618,49 @@ void InnerWidget::fill() { widget->setChartData(data.topHoursGraph.chart, Type::Bar); widget->setTitle(tr::lng_channel_earn_chart_top_hours()); + hasPreviousChart = true; } if (data.revenueGraph.chart) { - Ui::AddSkip(container); - Ui::AddDivider(container); - Ui::AddSkip(container); - Ui::AddSkip(container); + if (hasPreviousChart) { + Ui::AddSkip(container); + Ui::AddDivider(container); + Ui::AddSkip(container); + Ui::AddSkip(container); + } const auto widget = container->add( object_ptr(container), st::statisticsLayerMargins); - auto chart = data.revenueGraph.chart; - chart.currencyRate = multiplier; - - widget->setChartData(chart, Type::StackBar); + widget->setChartData([&] { + auto chart = data.revenueGraph.chart; + chart.currencyRate = multiplier; + return chart; + }(), Type::StackBar); widget->setTitle(tr::lng_channel_earn_chart_revenue()); + hasPreviousChart = true; + } + if (creditsData.revenueGraph.chart) { + if (hasPreviousChart) { + Ui::AddSkip(container); + Ui::AddDivider(container); + Ui::AddSkip(container); + Ui::AddSkip(container); + } + const auto widget = container->add( + object_ptr(container), + st::statisticsLayerMargins); + + widget->setChartData([&] { + auto chart = creditsData.revenueGraph.chart; + chart.currencyRate = creditsData.usdRate; + return chart; + }(), Type::StackBar); + widget->setTitle(tr::lng_bot_earn_chart_revenue()); } } - if (data.topHoursGraph.chart || data.revenueGraph.chart) { + if (data.topHoursGraph.chart + || data.revenueGraph.chart + || creditsData.revenueGraph.chart) { Ui::AddSkip(container); Ui::AddSkip(container); Ui::AddDivider(container); From 57d62423b3f22e0948307ea1eab555a1ab867b12 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 22 Jun 2024 07:12:10 +0300 Subject: [PATCH 097/142] Moved out custom slider class with natural width to single place. --- .../info/bot/earn/info_earn_inner_widget.cpp | 23 ++++----------- .../boosts/info_boosts_inner_widget.cpp | 23 ++++----------- .../SourceFiles/settings/settings_credits.cpp | 23 ++++----------- .../ui/widgets/slider_natural_width.h | 29 +++++++++++++++++++ Telegram/cmake/td_ui.cmake | 1 + 5 files changed, 45 insertions(+), 54 deletions(-) create mode 100644 Telegram/SourceFiles/ui/widgets/slider_natural_width.h diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp index 5605dfce2..f2371bd22 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp @@ -36,10 +36,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/discrete_sliders.h" #include "ui/widgets/fields/number_input.h" #include "ui/widgets/label_with_custom_emoji.h" #include "ui/widgets/labels.h" +#include "ui/widgets/slider_natural_width.h" #include "ui/wrap/slide_wrap.h" #include "styles/style_boxes.h" #include "styles/style_channel_earn.h" @@ -577,25 +577,12 @@ void InnerWidget::fillHistory() { header->setSubTitle({}); } - class Slider final : public Ui::SettingsSlider { - public: - using Ui::SettingsSlider::SettingsSlider; - void setNaturalWidth(int w) { - _naturalWidth = w; - } - int naturalWidth() const override { - return _naturalWidth; - } - - private: - int _naturalWidth = 0; - - }; - const auto slider = inner->add( - object_ptr>( + object_ptr>( inner, - object_ptr(inner, st::defaultTabsSlider)), + object_ptr( + inner, + st::defaultTabsSlider)), st::boxRowPadding); slider->toggle(!hasOneTab, anim::type::instant); diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/info_boosts_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/boosts/info_boosts_inner_widget.cpp index 15c72c5f2..6234ab010 100644 --- a/Telegram/SourceFiles/info/channel_statistics/boosts/info_boosts_inner_widget.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/boosts/info_boosts_inner_widget.cpp @@ -32,8 +32,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rect.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/discrete_sliders.h" #include "ui/widgets/labels.h" +#include "ui/widgets/slider_natural_width.h" #include "ui/wrap/slide_wrap.h" #include "styles/style_giveaway.h" #include "styles/style_info.h" @@ -423,25 +423,12 @@ void InnerWidget::fill() { header->setSubTitle({}); } - class Slider final : public Ui::SettingsSlider { - public: - using Ui::SettingsSlider::SettingsSlider; - void setNaturalWidth(int w) { - _naturalWidth = w; - } - int naturalWidth() const override { - return _naturalWidth; - } - - private: - int _naturalWidth = 0; - - }; - const auto slider = inner->add( - object_ptr>( + object_ptr>( inner, - object_ptr(inner, st::defaultTabsSlider)), + object_ptr( + inner, + st::defaultTabsSlider)), st::boxRowPadding); slider->toggle(!hasOneTab, anim::type::instant); diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 08d7231c3..0890f2703 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -31,7 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/discrete_sliders.h" +#include "ui/widgets/slider_natural_width.h" #include "ui/wrap/fade_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" @@ -160,25 +160,12 @@ void Credits::setupHistory(not_null container) { header->setSubTitle({}); } - class Slider final : public Ui::SettingsSlider { - public: - using Ui::SettingsSlider::SettingsSlider; - void setNaturalWidth(int w) { - _naturalWidth = w; - } - int naturalWidth() const override { - return _naturalWidth; - } - - private: - int _naturalWidth = 0; - - }; - const auto slider = inner->add( - object_ptr>( + object_ptr>( inner, - object_ptr(inner, st::defaultTabsSlider)), + object_ptr( + inner, + st::defaultTabsSlider)), st::boxRowPadding); slider->toggle(!hasOneTab, anim::type::instant); diff --git a/Telegram/SourceFiles/ui/widgets/slider_natural_width.h b/Telegram/SourceFiles/ui/widgets/slider_natural_width.h new file mode 100644 index 000000000..f2c52aa0a --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/slider_natural_width.h @@ -0,0 +1,29 @@ +/* +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 "ui/widgets/discrete_sliders.h" + +namespace Ui { + +class CustomWidthSlider final : public SettingsSlider { +public: + using Ui::SettingsSlider::SettingsSlider; + void setNaturalWidth(int w) { + _naturalWidth = w; + } + int naturalWidth() const override { + return _naturalWidth; + } + +private: + int _naturalWidth = 0; + +}; + +} // namespace Ui diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 34063bb22..f5cd963c5 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -404,6 +404,7 @@ PRIVATE ui/widgets/multi_select.h ui/widgets/sent_code_field.cpp ui/widgets/sent_code_field.h + ui/widgets/slider_natural_width.h ui/widgets/vertical_drum_picker.cpp ui/widgets/vertical_drum_picker.h From b79c306bfea023d2b7b5865a0bbb2a1cfcd00cef Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 23 Jun 2024 05:10:28 +0300 Subject: [PATCH 098/142] Added list of credits history to section of channel earn. --- .../earn/info_earn_inner_widget.cpp | 138 ++++++++++++++++-- .../earn/info_earn_widget.h | 5 +- 2 files changed, 129 insertions(+), 14 deletions(-) diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp index 018ecb00a..38c4dabd0 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp @@ -26,11 +26,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/info_controller.h" #include "info/profile/info_profile_values.h" // Info::Profile::NameValue. #include "info/statistics/info_statistics_inner_widget.h" // FillLoading. +#include "info/statistics/info_statistics_list_controllers.h" #include "iv/iv_instance.h" #include "lang/lang_keys.h" #include "main/main_account.h" #include "main/main_app_config.h" #include "main/main_session.h" +#include "settings/settings_credits_graphics.h" #include "statistics/chart_widget.h" #include "ui/basic_click_handlers.h" #include "ui/boxes/boost_box.h" @@ -46,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/fields/input_field.h" #include "ui/widgets/label_with_custom_emoji.h" #include "ui/widgets/popup_menu.h" +#include "ui/widgets/slider_natural_width.h" #include "ui/wrap/slide_wrap.h" #include "styles/style_boxes.h" #include "styles/style_channel_earn.h" @@ -54,6 +57,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_layers.h" #include "styles/style_settings.h" #include "styles/style_statistics.h" +#include "styles/style_credits.h" #include "styles/style_window.h" // mainMenuToggleFourStrokes. #include @@ -263,12 +267,15 @@ void InnerWidget::load() { struct State final { State(not_null peer) : api(peer->asChannel()) - , apiCredits(peer) { + , apiCredits(peer) + , apiCreditsHistory(peer, true, true) { } Api::ChannelEarnStatistics api; Api::CreditsEarnStatistics apiCredits; + Api::CreditsHistory apiCreditsHistory; rpl::lifetime apiLifetime; rpl::lifetime apiCreditsLifetime; + rpl::lifetime apiPremiumBotLifetime; }; const auto state = lifetime().make_state(_peer); const auto api = lifetime().make_state( @@ -326,14 +333,24 @@ void InnerWidget::load() { state->api.request( ) | rpl::start_with_error_done(fail, [=] { _state.currencyEarn = state->api.data(); - state->apiCredits.request( - ) | rpl::start_with_error_done([=](const QString &error) { - fail(error); - finish(); - }, [=] { - _state.creditsEarn = state->apiCredits.data(); - finish(); - }, state->apiCreditsLifetime); + state->apiCreditsHistory.request({}, [=]( + const Data::CreditsStatusSlice &data) { + _state.creditsStatusSlice = data; + ::Api::PremiumPeerBot( + &_peer->session() + ) | rpl::start_with_next([=](not_null bot) { + _state.premiumBotId = bot->id; + state->apiCredits.request( + ) | rpl::start_with_error_done([=](const QString &error) { + fail(error); + finish(); + }, [=] { + _state.creditsEarn = state->apiCredits.data(); + finish(); + }, state->apiCreditsLifetime); + state->apiPremiumBotLifetime.destroy(); + }, state->apiPremiumBotLifetime); + }); }, state->apiLifetime); }, lifetime()); } @@ -894,12 +911,79 @@ void InnerWidget::fill() { : tr::lng_channel_earn_balance_about_temp); Ui::AddSkip(container); } - if (!data.firstHistorySlice.list.empty()) { - AddHeader(container, tr::lng_channel_earn_history_title); + + const auto hasCurrencyTab = !data.firstHistorySlice.list.empty(); + const auto hasCreditsTab = !_state.creditsStatusSlice.list.empty() + && _state.premiumBotId; + const auto hasOneTab = (hasCurrencyTab || hasCreditsTab) + && (hasCurrencyTab != hasCreditsTab); + + const auto currencyTabText = tr::lng_channel_earn_currency_history( + tr::now); + const auto creditsTabText = tr::lng_channel_earn_credits_history(tr::now); + + const auto slider = container->add( + object_ptr>( + container, + object_ptr( + container, + st::defaultTabsSlider)), + st::boxRowPadding); + slider->toggle(!hasOneTab, anim::type::instant); + + if (hasCurrencyTab) { + slider->entity()->addSection(currencyTabText); + } + if (hasCreditsTab) { + slider->entity()->addSection(creditsTabText); + } + + { + const auto &st = st::defaultTabsSlider; + slider->entity()->setNaturalWidth(0 + + (hasCurrencyTab + ? st.labelStyle.font->width(currencyTabText) + : 0) + + (hasCreditsTab + ? st.labelStyle.font->width(creditsTabText) + : 0) + + rect::m::sum::h(st::boxRowPadding)); + } + + if (hasOneTab) { + if (hasCurrencyTab) { + AddHeader(container, tr::lng_channel_earn_history_title); + } else if (hasCreditsTab) { + AddHeader(container, tr::lng_channel_earn_credits_history); + slider->entity()->setActiveSectionFast(1); + } + } + + const auto historyCurrencyList = container->add( + object_ptr>( + container, + object_ptr(container))); + const auto historyCreditsList = container->add( + object_ptr>( + container, + object_ptr(container))); + + rpl::single(slider->entity()->activeSection()) | rpl::then( + slider->entity()->sectionActivated() + ) | rpl::start_with_next([=](int index) { + if (index == 0) { + historyCurrencyList->toggle(true, anim::type::instant); + historyCreditsList->toggle(false, anim::type::instant); + } else if (index == 1) { + historyCurrencyList->toggle(false, anim::type::instant); + historyCreditsList->toggle(true, anim::type::instant); + } + }, container->lifetime()); + + if (hasCurrencyTab) { Ui::AddSkip(container); - const auto historyList = container->add( - object_ptr(container)); + const auto historyList = historyCurrencyList->entity(); const auto addHistoryEntry = [=]( const Data::EarnHistoryEntry &entry, const tr::phrase<> &text) { @@ -1208,6 +1292,34 @@ void InnerWidget::fill() { Ui::AddDivider(container); Ui::AddSkip(container); } + if (hasCreditsTab) { + const auto controller = _controller->parentController(); + const auto show = controller->uiShow(); + const auto premiumBot = _peer->owner().peer(_state.premiumBotId); + const auto entryClicked = [=](const Data::CreditsHistoryEntry &e) { + show->show(Box( + ::Settings::ReceiptCreditsBox, + controller, + premiumBot.get(), + e)); + }; + + const auto star = historyCreditsList->lifetime().make_state( + Ui::GenerateStars(st::creditsTopupButton.height, 1)); + + Info::Statistics::AddCreditsHistoryList( + show, + _state.creditsStatusSlice, + historyCreditsList->entity(), + entryClicked, + premiumBot, + star, + true, + true); + Ui::AddSkip(container); + Ui::AddDivider(container); + Ui::AddSkip(container); + } if (channel) { //constexpr auto kMaxCPM = 50; // Debug. const auto requiredLevel = Data::LevelLimits(session) diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_widget.h b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_widget.h index 40e0f3f2c..5979c5c37 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_widget.h +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_widget.h @@ -7,8 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "data/data_channel_earn.h" #include "data/data_bot_earn.h" +#include "data/data_channel_earn.h" +#include "data/data_credits.h" #include "info/info_content_widget.h" namespace Info::ChannelEarn { @@ -31,6 +32,8 @@ public: struct SavedState final { Data::EarnStatistics currencyEarn; Data::CreditsEarnStatistics creditsEarn; + Data::CreditsStatusSlice creditsStatusSlice; + PeerId premiumBotId = PeerId(0); }; void setState(SavedState states); From a84ac933dde357eec1f0f16ef9cecc600f04ec4f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 23 Jun 2024 06:48:57 +0300 Subject: [PATCH 099/142] Added support of api update to history lists in section of channel earn. --- Telegram/Resources/langs/lang.strings | 2 + .../earn/info_earn_inner_widget.cpp | 796 ++++++++++-------- 2 files changed, 426 insertions(+), 372 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index ff021318f..2ca7529f3 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -5214,6 +5214,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_channel_earn_chart_revenue" = "Ad revenue"; "lng_channel_earn_chart_overriden_detail_currency" = "Revenue in TON"; "lng_channel_earn_chart_overriden_detail_usd" = "Revenue in USD"; +"lng_channel_earn_currency_history" = "TON Transactions"; +"lng_channel_earn_credits_history" = "Stars Transactions"; "lng_bot_earn_title" = "Stars Balance"; "lng_bot_earn_chart_revenue" = "Revenue"; diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp index 38c4dabd0..333cb47ff 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp @@ -697,7 +697,11 @@ void InnerWidget::fill() { const auto majorLabel = Ui::CreateChild( line, st::channelEarnOverviewMajorLabel); - addEmojiToMajor(majorLabel, rpl::duplicate(currencyValue), {}, {}); + addEmojiToMajor( + majorLabel, + rpl::duplicate(currencyValue), + {}, + {}); const auto minorLabel = Ui::CreateChild( line, rpl::duplicate(currencyValue) | rpl::map(MinorPart), @@ -912,414 +916,462 @@ void InnerWidget::fill() { Ui::AddSkip(container); } - const auto hasCurrencyTab = !data.firstHistorySlice.list.empty(); - const auto hasCreditsTab = !_state.creditsStatusSlice.list.empty() - && _state.premiumBotId; - const auto hasOneTab = (hasCurrencyTab || hasCreditsTab) - && (hasCurrencyTab != hasCreditsTab); + const auto sectionIndex = container->lifetime().make_state(0); + const auto rebuildLists = [=]( + const Memento::SavedState &data, + not_null listsContainer) { + const auto hasCurrencyTab + = !data.currencyEarn.firstHistorySlice.list.empty(); + const auto hasCreditsTab = !data.creditsStatusSlice.list.empty() + && data.premiumBotId; + const auto hasOneTab = (hasCurrencyTab || hasCreditsTab) + && (hasCurrencyTab != hasCreditsTab); - const auto currencyTabText = tr::lng_channel_earn_currency_history( - tr::now); - const auto creditsTabText = tr::lng_channel_earn_credits_history(tr::now); + const auto currencyTabText = tr::lng_channel_earn_currency_history( + tr::now); + const auto creditsTabText = tr::lng_channel_earn_credits_history( + tr::now); - const auto slider = container->add( - object_ptr>( - container, - object_ptr( - container, - st::defaultTabsSlider)), - st::boxRowPadding); - slider->toggle(!hasOneTab, anim::type::instant); + const auto slider = listsContainer->add( + object_ptr>( + listsContainer, + object_ptr( + listsContainer, + st::defaultTabsSlider)), + st::boxRowPadding); + slider->toggle(!hasOneTab, anim::type::instant); - if (hasCurrencyTab) { - slider->entity()->addSection(currencyTabText); - } - if (hasCreditsTab) { - slider->entity()->addSection(creditsTabText); - } - - { - const auto &st = st::defaultTabsSlider; - slider->entity()->setNaturalWidth(0 - + (hasCurrencyTab - ? st.labelStyle.font->width(currencyTabText) - : 0) - + (hasCreditsTab - ? st.labelStyle.font->width(creditsTabText) - : 0) - + rect::m::sum::h(st::boxRowPadding)); - } - - if (hasOneTab) { if (hasCurrencyTab) { - AddHeader(container, tr::lng_channel_earn_history_title); - } else if (hasCreditsTab) { - AddHeader(container, tr::lng_channel_earn_credits_history); - slider->entity()->setActiveSectionFast(1); + slider->entity()->addSection(currencyTabText); } - } - - const auto historyCurrencyList = container->add( - object_ptr>( - container, - object_ptr(container))); - const auto historyCreditsList = container->add( - object_ptr>( - container, - object_ptr(container))); - - rpl::single(slider->entity()->activeSection()) | rpl::then( - slider->entity()->sectionActivated() - ) | rpl::start_with_next([=](int index) { - if (index == 0) { - historyCurrencyList->toggle(true, anim::type::instant); - historyCreditsList->toggle(false, anim::type::instant); - } else if (index == 1) { - historyCurrencyList->toggle(false, anim::type::instant); - historyCreditsList->toggle(true, anim::type::instant); + if (hasCreditsTab) { + slider->entity()->addSection(creditsTabText); } - }, container->lifetime()); - if (hasCurrencyTab) { - Ui::AddSkip(container); + { + const auto &st = st::defaultTabsSlider; + slider->entity()->setNaturalWidth(0 + + (hasCurrencyTab + ? st.labelStyle.font->width(currencyTabText) + : 0) + + (hasCreditsTab + ? st.labelStyle.font->width(creditsTabText) + : 0) + + rect::m::sum::h(st::boxRowPadding)); + } - const auto historyList = historyCurrencyList->entity(); - const auto addHistoryEntry = [=]( - const Data::EarnHistoryEntry &entry, - const tr::phrase<> &text) { - const auto wrap = historyList->add( - object_ptr>( - historyList, - object_ptr(historyList), - QMargins())); - const auto inner = wrap->entity(); - inner->setAttribute(Qt::WA_TransparentForMouseEvents); - inner->add(object_ptr( - inner, - text(), - st::channelEarnSemiboldLabel)); - - const auto isIn = entry.type == Data::EarnHistoryEntry::Type::In; - const auto recipient = Ui::Text::Wrapped( - { entry.provider }, - EntityType::Code); - if (!recipient.text.isEmpty()) { - Ui::AddSkip(inner, st::channelEarnHistoryThreeSkip); - const auto label = inner->add(object_ptr( - inner, - rpl::single(recipient), - st::channelEarnHistoryRecipientLabel)); - label->setBreakEverywhere(true); - label->setTryMakeSimilarLines(true); - Ui::AddSkip(inner, st::channelEarnHistoryThreeSkip); - } else { - Ui::AddSkip(inner, st::channelEarnHistoryTwoSkip); + if (hasOneTab) { + if (hasCurrencyTab) { + AddHeader(listsContainer, tr::lng_channel_earn_history_title); + } else if (hasCreditsTab) { + AddHeader( + listsContainer, + tr::lng_channel_earn_credits_history); + slider->entity()->setActiveSectionFast(1); } + } else { + slider->entity()->setActiveSectionFast(*sectionIndex); + } - const auto isFailed = entry.status - == Data::EarnHistoryEntry::Status::Failed; - const auto isPending = entry.status - == Data::EarnHistoryEntry::Status::Pending; - const auto dateText = (!entry.dateTo.isNull() || isFailed) - ? (FormatDate(entry.date) - + ' ' - + QChar(8212) - + ' ' - + (isFailed - ? tr::lng_channel_earn_history_out_failed(tr::now) - : FormatDate(entry.dateTo))) - : isPending - ? tr::lng_channel_earn_history_pending(tr::now) - : FormatDate(entry.date); - inner->add(object_ptr( - inner, - dateText, - st::channelEarnHistorySubLabel) - )->setTextColorOverride(isFailed - ? std::make_optional(st::menuIconAttentionColor->c) - : std::nullopt); + const auto tabCurrencyList = listsContainer->add( + object_ptr>( + listsContainer, + object_ptr(listsContainer))); + const auto tabCreditsList = listsContainer->add( + object_ptr>( + listsContainer, + object_ptr(listsContainer))); - const auto color = (isIn - ? st::boxTextFgGood - : st::menuIconAttentionColor)->c; - const auto majorLabel = Ui::CreateChild( - wrap, - st::channelEarnHistoryMajorLabel); - addEmojiToMajor(majorLabel, rpl::single(entry.amount), isIn, {}); - majorLabel->setAttribute(Qt::WA_TransparentForMouseEvents); - majorLabel->setTextColorOverride(color); - const auto minorText = MinorPart(entry.amount); - const auto minorLabel = Ui::CreateChild( - wrap, - rpl::single(minorText), - st::channelEarnHistoryMinorLabel); - minorLabel->setAttribute(Qt::WA_TransparentForMouseEvents); - minorLabel->setTextColorOverride(color); - const auto button = Ui::CreateChild( - wrap, - rpl::single(QString())); - Ui::ToggleChildrenVisibility(wrap, true); + rpl::single(slider->entity()->activeSection()) | rpl::then( + slider->entity()->sectionActivated() + ) | rpl::start_with_next([=](int index) { + if (index == 0) { + tabCurrencyList->toggle(true, anim::type::instant); + tabCreditsList->toggle(false, anim::type::instant); + } else if (index == 1) { + tabCurrencyList->toggle(false, anim::type::instant); + tabCreditsList->toggle(true, anim::type::instant); + } + *sectionIndex = index; + }, listsContainer->lifetime()); - const auto detailsBox = [=, amount = entry.amount, peer = _peer]( - not_null box) { - box->addTopButton( - st::boxTitleClose, - [=] { box->closeBox(); }); - Ui::AddSkip(box->verticalLayout()); - Ui::AddSkip(box->verticalLayout()); - const auto labels = box->addRow( - object_ptr>( - box, - object_ptr(box)))->entity(); + if (hasCurrencyTab) { + Ui::AddSkip(listsContainer); + const auto historyList = tabCurrencyList->entity(); + const auto addHistoryEntry = [=]( + const Data::EarnHistoryEntry &entry, + const tr::phrase<> &text) { + const auto wrap = historyList->add( + object_ptr>( + historyList, + object_ptr(historyList), + QMargins())); + const auto inner = wrap->entity(); + inner->setAttribute(Qt::WA_TransparentForMouseEvents); + inner->add(object_ptr( + inner, + text(), + st::channelEarnSemiboldLabel)); + + const auto isIn + = (entry.type == Data::EarnHistoryEntry::Type::In); + const auto recipient = Ui::Text::Wrapped( + { entry.provider }, + EntityType::Code); + if (!recipient.text.isEmpty()) { + Ui::AddSkip(inner, st::channelEarnHistoryThreeSkip); + const auto label = inner->add(object_ptr( + inner, + rpl::single(recipient), + st::channelEarnHistoryRecipientLabel)); + label->setBreakEverywhere(true); + label->setTryMakeSimilarLines(true); + Ui::AddSkip(inner, st::channelEarnHistoryThreeSkip); + } else { + Ui::AddSkip(inner, st::channelEarnHistoryTwoSkip); + } + + const auto isFailed = entry.status + == Data::EarnHistoryEntry::Status::Failed; + const auto isPending = entry.status + == Data::EarnHistoryEntry::Status::Pending; + const auto dateText = (!entry.dateTo.isNull() || isFailed) + ? (FormatDate(entry.date) + + ' ' + + QChar(8212) + + ' ' + + (isFailed + ? tr::lng_channel_earn_history_out_failed(tr::now) + : FormatDate(entry.dateTo))) + : isPending + ? tr::lng_channel_earn_history_pending(tr::now) + : FormatDate(entry.date); + inner->add(object_ptr( + inner, + dateText, + st::channelEarnHistorySubLabel) + )->setTextColorOverride(isFailed + ? std::make_optional( + st::menuIconAttentionColor->c) + : std::nullopt); + + const auto color = (isIn + ? st::boxTextFgGood + : st::menuIconAttentionColor)->c; const auto majorLabel = Ui::CreateChild( - labels, - st::channelEarnOverviewMajorLabel); - addEmojiToMajor(majorLabel, rpl::single(amount), isIn, {}); + wrap, + st::channelEarnHistoryMajorLabel); + addEmojiToMajor( + majorLabel, + rpl::single(entry.amount), + isIn, + {}); majorLabel->setAttribute(Qt::WA_TransparentForMouseEvents); majorLabel->setTextColorOverride(color); + const auto minorText = MinorPart(entry.amount); const auto minorLabel = Ui::CreateChild( - labels, - minorText, - st::channelEarnOverviewMinorLabel); + wrap, + rpl::single(minorText), + st::channelEarnHistoryMinorLabel); minorLabel->setAttribute(Qt::WA_TransparentForMouseEvents); minorLabel->setTextColorOverride(color); - rpl::combine( - majorLabel->sizeValue(), - minorLabel->sizeValue() - ) | rpl::start_with_next([=]( - const QSize &majorSize, - const QSize &minorSize) { - labels->resize( - majorSize.width() + minorSize.width(), - majorSize.height()); - majorLabel->moveToLeft(0, 0); - minorLabel->moveToRight( - 0, - st::channelEarnOverviewMinorLabelSkip); - }, box->lifetime()); + const auto button = Ui::CreateChild( + wrap, + rpl::single(QString())); + Ui::ToggleChildrenVisibility(wrap, true); - Ui::AddSkip(box->verticalLayout()); - box->addRow(object_ptr>( - box, - object_ptr( - box, - dateText, - st::channelEarnHistorySubLabel))); - Ui::AddSkip(box->verticalLayout()); - Ui::AddSkip(box->verticalLayout()); - Ui::AddSkip(box->verticalLayout()); - box->addRow(object_ptr>( - box, - object_ptr( - box, - isIn - ? tr::lng_channel_earn_history_in_about() - : tr::lng_channel_earn_history_out(), - st::channelEarnHistoryDescriptionLabel))); - Ui::AddSkip(box->verticalLayout()); - if (isIn) { + const auto detailsBox = [=, peer = _peer]( + not_null box) { + box->addTopButton( + st::boxTitleClose, + [=] { box->closeBox(); }); Ui::AddSkip(box->verticalLayout()); - } - - if (!recipient.text.isEmpty()) { - AddRecipient(box, recipient); - } - if (isIn) { - const auto peerBubble = box->addRow( - object_ptr>( + Ui::AddSkip(box->verticalLayout()); + const auto labels = box->addRow( + object_ptr>( box, object_ptr(box)))->entity(); - peerBubble->setAttribute( + + const auto majorLabel = Ui::CreateChild( + labels, + st::channelEarnOverviewMajorLabel); + addEmojiToMajor( + majorLabel, + rpl::single(entry.amount), + isIn, + {}); + majorLabel->setAttribute( Qt::WA_TransparentForMouseEvents); - const auto left = Ui::CreateChild( - peerBubble, - peer, - st::uploadUserpicButton); - const auto right = Ui::CreateChild( - peerBubble, - Info::Profile::NameValue(peer), - st::channelEarnSemiboldLabel); + majorLabel->setTextColorOverride(color); + const auto minorLabel = Ui::CreateChild( + labels, + minorText, + st::channelEarnOverviewMinorLabel); + minorLabel->setAttribute( + Qt::WA_TransparentForMouseEvents); + minorLabel->setTextColorOverride(color); rpl::combine( - left->sizeValue(), - right->sizeValue() + majorLabel->sizeValue(), + minorLabel->sizeValue() ) | rpl::start_with_next([=]( - const QSize &leftSize, - const QSize &rightSize) { - const auto padding = QMargins( - st::chatGiveawayPeerPadding.left() * 2, - st::chatGiveawayPeerPadding.top(), - st::chatGiveawayPeerPadding.right(), - st::chatGiveawayPeerPadding.bottom()); - peerBubble->resize( - leftSize.width() - + rightSize.width() - + rect::m::sum::h(padding), - leftSize.height()); - left->moveToLeft(0, 0); - right->moveToRight(padding.right(), padding.top()); - const auto maxRightSize = box->width() - - rect::m::sum::h(st::boxRowPadding) - - rect::m::sum::h(padding) - - leftSize.width(); - if (rightSize.width() > maxRightSize) { - right->resizeToWidth(maxRightSize); - } - }, peerBubble->lifetime()); - peerBubble->paintRequest( - ) | rpl::start_with_next([=] { - auto p = QPainter(peerBubble); - auto hq = PainterHighQualityEnabler(p); - p.setPen(Qt::NoPen); - p.setBrush(st::windowBgOver); - const auto rect = peerBubble->rect(); - const auto radius = rect.height() / 2; - p.drawRoundedRect(rect, radius, radius); - }, peerBubble->lifetime()); - } - { - const auto &st = st::premiumPreviewDoubledLimitsBox; - box->setStyle(st); - auto button = object_ptr( - container, - (!entry.successLink.isEmpty()) - ? tr::lng_channel_earn_history_out_button() - : tr::lng_box_ok(), - st::defaultActiveButton); - button->resizeToWidth(box->width() - - st.buttonPadding.left() - - st.buttonPadding.left()); - if (!entry.successLink.isEmpty()) { - button->setAcceptBoth(); - button->addClickHandler([=](Qt::MouseButton button) { - if (button == Qt::LeftButton) { - UrlClickHandler::Open(entry.successLink); - } else if (button == Qt::RightButton) { - ShowMenu(box, entry.successLink); - } - }); - } else { - button->setClickedCallback([=] { box->closeBox(); }); + const QSize &majorSize, + const QSize &minorSize) { + labels->resize( + majorSize.width() + minorSize.width(), + majorSize.height()); + majorLabel->moveToLeft(0, 0); + minorLabel->moveToRight( + 0, + st::channelEarnOverviewMinorLabelSkip); + }, box->lifetime()); + + Ui::AddSkip(box->verticalLayout()); + box->addRow(object_ptr>( + box, + object_ptr( + box, + dateText, + st::channelEarnHistorySubLabel))); + Ui::AddSkip(box->verticalLayout()); + Ui::AddSkip(box->verticalLayout()); + Ui::AddSkip(box->verticalLayout()); + box->addRow(object_ptr>( + box, + object_ptr( + box, + isIn + ? tr::lng_channel_earn_history_in_about() + : tr::lng_channel_earn_history_out(), + st::channelEarnHistoryDescriptionLabel))); + Ui::AddSkip(box->verticalLayout()); + if (isIn) { + Ui::AddSkip(box->verticalLayout()); } - box->addButton(std::move(button)); - } - Ui::AddSkip(box->verticalLayout()); - Ui::AddSkip(box->verticalLayout()); - box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); }); - }; - button->setClickedCallback([=] { - _show->showBox(Box(detailsBox)); - }); - wrap->geometryValue( - ) | rpl::start_with_next([=](const QRect &g) { - const auto &padding = st::boxRowPadding; - const auto majorTop = (g.height() - majorLabel->height()) / 2; - minorLabel->moveToRight( - padding.right(), - majorTop + st::channelEarnHistoryMinorLabelSkip); - majorLabel->moveToRight( - padding.right() + minorLabel->width(), - majorTop); - const auto rightWrapPadding = rect::m::sum::h(padding) - + minorLabel->width() - + majorLabel->width(); - wrap->setPadding( - st::channelEarnHistoryOuter + if (!recipient.text.isEmpty()) { + AddRecipient(box, recipient); + } + if (isIn) { + const auto peerBubble = box->addRow( + object_ptr>( + box, + object_ptr(box)))->entity(); + peerBubble->setAttribute( + Qt::WA_TransparentForMouseEvents); + const auto left = Ui::CreateChild( + peerBubble, + peer, + st::uploadUserpicButton); + const auto right = Ui::CreateChild( + peerBubble, + Info::Profile::NameValue(peer), + st::channelEarnSemiboldLabel); + rpl::combine( + left->sizeValue(), + right->sizeValue() + ) | rpl::start_with_next([=]( + const QSize &leftSize, + const QSize &rightSize) { + const auto padding = QMargins( + st::chatGiveawayPeerPadding.left() * 2, + st::chatGiveawayPeerPadding.top(), + st::chatGiveawayPeerPadding.right(), + st::chatGiveawayPeerPadding.bottom()); + peerBubble->resize( + leftSize.width() + + rightSize.width() + + rect::m::sum::h(padding), + leftSize.height()); + left->moveToLeft(0, 0); + right->moveToRight( + padding.right(), + padding.top()); + const auto maxRightSize = box->width() + - rect::m::sum::h(st::boxRowPadding) + - rect::m::sum::h(padding) + - leftSize.width(); + if (rightSize.width() > maxRightSize) { + right->resizeToWidth(maxRightSize); + } + }, peerBubble->lifetime()); + peerBubble->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(peerBubble); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(st::windowBgOver); + const auto rect = peerBubble->rect(); + const auto radius = rect.height() / 2; + p.drawRoundedRect(rect, radius, radius); + }, peerBubble->lifetime()); + } + const auto closeBox = [=] { box->closeBox(); }; + { + const auto &st = st::premiumPreviewDoubledLimitsBox; + box->setStyle(st); + auto button = object_ptr( + box, + (!entry.successLink.isEmpty()) + ? tr::lng_channel_earn_history_out_button() + : tr::lng_box_ok(), + st::defaultActiveButton); + button->resizeToWidth(box->width() + - st.buttonPadding.left() + - st.buttonPadding.left()); + if (!entry.successLink.isEmpty()) { + button->setAcceptBoth(); + button->addClickHandler([=]( + Qt::MouseButton button) { + if (button == Qt::LeftButton) { + UrlClickHandler::Open(entry.successLink); + } else if (button == Qt::RightButton) { + ShowMenu(box, entry.successLink); + } + }); + } else { + button->setClickedCallback(closeBox); + } + box->addButton(std::move(button)); + } + Ui::AddSkip(box->verticalLayout()); + Ui::AddSkip(box->verticalLayout()); + box->addButton(tr::lng_box_ok(), closeBox); + }; + + button->setClickedCallback([=] { + _show->showBox(Box(detailsBox)); + }); + wrap->geometryValue( + ) | rpl::start_with_next([=](const QRect &g) { + const auto &padding = st::boxRowPadding; + const auto majorTop = (g.height() - majorLabel->height()) + / 2; + minorLabel->moveToRight( + padding.right(), + majorTop + st::channelEarnHistoryMinorLabelSkip); + majorLabel->moveToRight( + padding.right() + minorLabel->width(), + majorTop); + const auto rightWrapPadding = rect::m::sum::h(padding) + + minorLabel->width() + + majorLabel->width(); + wrap->setPadding(st::channelEarnHistoryOuter + QMargins(padding.left(), 0, rightWrapPadding, 0)); - button->resize(g.size()); - button->lower(); - }, wrap->lifetime()); - }; - const auto handleSlice = [=](const Data::EarnHistorySlice &slice) { - for (const auto &entry : slice.list) { - addHistoryEntry( - entry, - (entry.type == Data::EarnHistoryEntry::Type::In) - ? tr::lng_channel_earn_history_in - : (entry.type == Data::EarnHistoryEntry::Type::Return) - ? tr::lng_channel_earn_history_return - : tr::lng_channel_earn_history_out); - } - historyList->resizeToWidth(container->width()); - }; - handleSlice(data.firstHistorySlice); - if (!data.firstHistorySlice.allLoaded) { - struct ShowMoreState final { - ShowMoreState(not_null channel) - : api(channel) { + button->resize(g.size()); + button->lower(); + }, wrap->lifetime()); + }; + const auto handleSlice = [=](const Data::EarnHistorySlice &s) { + using Type = Data::EarnHistoryEntry::Type; + for (const auto &entry : s.list) { + addHistoryEntry( + entry, + (entry.type == Type::In) + ? tr::lng_channel_earn_history_in + : (entry.type == Type::Return) + ? tr::lng_channel_earn_history_return + : tr::lng_channel_earn_history_out); } - Api::ChannelEarnStatistics api; - bool loading = false; - Data::EarnHistorySlice::OffsetToken token; - rpl::variable showed = 0; + historyList->resizeToWidth(listsContainer->width()); }; - const auto state = lifetime().make_state(channel); - state->token = data.firstHistorySlice.token; - state->showed = data.firstHistorySlice.list.size(); - const auto max = data.firstHistorySlice.total; - const auto wrap = container->add( - object_ptr>( - container, - object_ptr( - container, - tr::lng_channel_earn_history_show_more( - lt_count, - state->showed.value( - ) | rpl::map( - max - rpl::mappers::_1 - ) | tr::to_count()), - st::statisticsShowMoreButton))); - const auto button = wrap->entity(); - AddArrow(button); + const auto &firstSlice = data.currencyEarn.firstHistorySlice; + handleSlice(firstSlice); + if (!firstSlice.allLoaded) { + struct ShowMoreState final { + ShowMoreState(not_null channel) + : api(channel) { + } + Api::ChannelEarnStatistics api; + bool loading = false; + Data::EarnHistorySlice::OffsetToken token; + rpl::variable showed = 0; + }; + const auto state + = lifetime().make_state(channel); + state->token = firstSlice.token; + state->showed = firstSlice.list.size(); + const auto max = firstSlice.total; + const auto wrap = listsContainer->add( + object_ptr>( + listsContainer, + object_ptr( + listsContainer, + tr::lng_channel_earn_history_show_more( + lt_count, + state->showed.value( + ) | rpl::map( + max - rpl::mappers::_1 + ) | tr::to_count()), + st::statisticsShowMoreButton))); + const auto button = wrap->entity(); + AddArrow(button); - wrap->toggle(true, anim::type::instant); - const auto handleReceived = [=](Data::EarnHistorySlice slice) { - state->loading = false; - handleSlice(slice); - wrap->toggle(!slice.allLoaded, anim::type::instant); - state->token = slice.token; - state->showed = state->showed.current() + slice.list.size(); - }; - button->setClickedCallback([=] { - if (!state->loading) { + wrap->toggle(true, anim::type::instant); + const auto handleReceived = [=]( + Data::EarnHistorySlice slice) { + state->loading = false; + handleSlice(slice); + wrap->toggle(!slice.allLoaded, anim::type::instant); + state->token = slice.token; + state->showed = state->showed.current() + + slice.list.size(); + }; + button->setClickedCallback([=] { + if (state->loading) { + return; + } state->loading = true; state->api.requestHistory(state->token, handleReceived); - } - }); + }); + } } - Ui::AddSkip(container); - Ui::AddDivider(container); - Ui::AddSkip(container); - } - if (hasCreditsTab) { - const auto controller = _controller->parentController(); - const auto show = controller->uiShow(); - const auto premiumBot = _peer->owner().peer(_state.premiumBotId); - const auto entryClicked = [=](const Data::CreditsHistoryEntry &e) { - show->show(Box( - ::Settings::ReceiptCreditsBox, - controller, - premiumBot.get(), - e)); - }; + if (hasCreditsTab) { + const auto controller = _controller->parentController(); + const auto show = controller->uiShow(); + const auto premiumBot = _peer->owner().peer(data.premiumBotId); + const auto entryClicked = [=]( + const Data::CreditsHistoryEntry &e) { + show->show(Box( + ::Settings::ReceiptCreditsBox, + controller, + premiumBot.get(), + e)); + }; - const auto star = historyCreditsList->lifetime().make_state( - Ui::GenerateStars(st::creditsTopupButton.height, 1)); + const auto star = tabCreditsList->lifetime().make_state( + Ui::GenerateStars(st::creditsTopupButton.height, 1)); + + Info::Statistics::AddCreditsHistoryList( + show, + data.creditsStatusSlice, + tabCreditsList->entity(), + entryClicked, + premiumBot, + star, + true, + true); + } + if (hasCurrencyTab || hasCreditsTab) { + Ui::AddSkip(listsContainer); + Ui::AddDivider(listsContainer); + Ui::AddSkip(listsContainer); + } + }; + + const auto historyContainer = container->add( + object_ptr(container)); + rpl::single(rpl::empty) | rpl::then( + _stateUpdated.events() + ) | rpl::start_with_next([=] { + const auto listsContainer = historyContainer->add( + object_ptr(container)); + rebuildLists(_state, listsContainer); + while (historyContainer->count() > 1) { + delete historyContainer->widgetAt(0); + } + }, historyContainer->lifetime()); - Info::Statistics::AddCreditsHistoryList( - show, - _state.creditsStatusSlice, - historyCreditsList->entity(), - entryClicked, - premiumBot, - star, - true, - true); - Ui::AddSkip(container); - Ui::AddDivider(container); - Ui::AddSkip(container); - } if (channel) { //constexpr auto kMaxCPM = 50; // Debug. const auto requiredLevel = Data::LevelLimits(session) From 93aebc747d46d36d196adf0bcb0987b717e9749f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 23 Jun 2024 19:44:58 +0300 Subject: [PATCH 100/142] Moved out special input for credits to td_ui. --- .../info/bot/earn/info_earn_inner_widget.cpp | 38 ++------------- .../ui/effects/credits_graphics.cpp | 47 ++++++++++++++++++- .../SourceFiles/ui/effects/credits_graphics.h | 6 +++ 3 files changed, 54 insertions(+), 37 deletions(-) diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp index f2371bd22..0ef6a9748 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp @@ -36,7 +36,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/fields/number_input.h" #include "ui/widgets/label_with_custom_emoji.h" #include "ui/widgets/labels.h" #include "ui/widgets/slider_natural_width.h" @@ -275,40 +274,9 @@ void InnerWidget::fill() { Ui::AddSkip(container); - const auto input = [&] { - const auto &st = st::botEarnInputField; - const auto inputContainer = container->add( - Ui::CreateSkipWidget(container, st.heightMin)); - const auto currentValue = rpl::variable( - rpl::duplicate(availableBalanceValue)); - const auto input = Ui::CreateChild( - inputContainer, - st, - tr::lng_bot_earn_out_ph(), - QString::number(currentValue.current()), - currentValue.current()); - rpl::duplicate( - availableBalanceValue - ) | rpl::start_with_next([=](uint64 v) { - input->changeLimit(v); - input->setText(QString::number(v)); - }, input->lifetime()); - const auto icon = Ui::CreateSingleStarWidget( - inputContainer, - st.style.font->height); - inputContainer->sizeValue( - ) | rpl::start_with_next([=](const QSize &size) { - input->resize( - size.width() - rect::m::sum::h(st::boxRowPadding), - st.heightMin); - input->moveToLeft(st::boxRowPadding.left(), 0); - icon->moveToLeft( - st::boxRowPadding.left(), - st.textMargins.top()); - }, input->lifetime()); - Ui::ToggleChildrenVisibility(inputContainer, true); - return input; - }(); + const auto input = Ui::AddInputFieldForCredits( + container, + rpl::duplicate(availableBalanceValue)); Ui::AddSkip(container); Ui::AddSkip(container); diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index 23b7503ea..60af867b1 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -21,10 +21,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/empty_userpic.h" #include "ui/painter.h" #include "ui/rect.h" +#include "ui/widgets/fields/number_input.h" +#include "ui/wrap/padding_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "styles/style_channel_earn.h" #include "styles/style_credits.h" -#include "styles/style_intro.h" // introFragmentIcon. -#include "styles/style_settings.h" #include "styles/style_dialogs.h" +#include "styles/style_intro.h" // introFragmentIcon. +#include "styles/style_layers.h" +#include "styles/style_settings.h" +#include "styles/style_widgets.h" #include @@ -105,6 +111,43 @@ not_null CreateSingleStarWidget( return widget; } +not_null AddInputFieldForCredits( + not_null container, + rpl::producer value) { + const auto &st = st::botEarnInputField; + const auto inputContainer = container->add( + CreateSkipWidget(container, st.heightMin)); + const auto currentValue = rpl::variable( + rpl::duplicate(value)); + const auto input = Ui::CreateChild( + inputContainer, + st, + tr::lng_bot_earn_out_ph(), + QString::number(currentValue.current()), + currentValue.current()); + rpl::duplicate( + value + ) | rpl::start_with_next([=](uint64 v) { + input->changeLimit(v); + input->setText(QString::number(v)); + }, input->lifetime()); + const auto icon = CreateSingleStarWidget( + inputContainer, + st.style.font->height); + inputContainer->sizeValue( + ) | rpl::start_with_next([=](const QSize &size) { + input->resize( + size.width() - rect::m::sum::h(st::boxRowPadding), + st.heightMin); + input->moveToLeft(st::boxRowPadding.left(), 0); + icon->moveToLeft( + st::boxRowPadding.left(), + st.textMargins.top()); + }, input->lifetime()); + Ui::ToggleChildrenVisibility(inputContainer, true); + return input; +} + PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( const Data::CreditsHistoryEntry &entry) { const auto bg = [&]() -> Ui::EmptyUserpic::BgColors { diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.h b/Telegram/SourceFiles/ui/effects/credits_graphics.h index eacc9148b..343910adf 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.h +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.h @@ -14,7 +14,9 @@ struct CreditsHistoryEntry; } // namespace Data namespace Ui { +class MaskedInputField; class RpWidget; +class VerticalLayout; } // namespace Ui namespace Ui { @@ -25,6 +27,10 @@ namespace Ui { not_null parent, int height); +[[nodiscard]] not_null AddInputFieldForCredits( + not_null container, + rpl::producer value); + Fn GenerateCreditsPaintUserpicCallback( const Data::CreditsHistoryEntry &entry); From 37181f9d0a659f192fbc1ac1acacefa420d5560b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 24 Jun 2024 02:15:15 +0300 Subject: [PATCH 101/142] Moved out special widget for credits input to single place. --- .../info/bot/earn/info_earn_inner_widget.cpp | 282 +---------------- .../settings/settings_credits_graphics.cpp | 285 +++++++++++++++++- .../settings/settings_credits_graphics.h | 9 + 3 files changed, 304 insertions(+), 272 deletions(-) diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp index 0ef6a9748..b6d37dc1e 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp @@ -8,9 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/bot/earn/info_earn_inner_widget.h" #include "api/api_credits.h" -#include "api/api_earn.h" #include "api/api_filter_updates.h" -#include "base/timer_rpl.h" #include "base/unixtime.h" #include "core/ui_integration.h" #include "data/data_channel_earn.h" @@ -24,7 +22,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/statistics/info_statistics_list_controllers.h" #include "lang/lang_keys.h" #include "main/main_account.h" -#include "main/main_app_config.h" #include "main/main_session.h" #include "settings/settings_credits_graphics.h" #include "statistics/chart_widget.h" @@ -32,7 +29,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/credits_graphics.h" #include "ui/layers/generic_box.h" #include "ui/rect.h" -#include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" @@ -42,20 +38,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/slide_wrap.h" #include "styles/style_boxes.h" #include "styles/style_channel_earn.h" -#include "styles/style_chat.h" #include "styles/style_credits.h" -#include "styles/style_layers.h" #include "styles/style_settings.h" #include "styles/style_statistics.h" namespace Info::BotEarn { namespace { -[[nodiscard]] int WithdrawalMin(not_null session) { - const auto key = u"stars_revenue_withdrawal_min"_q; - return session->appConfig().get(key, 1000); -} - void AddHeader( not_null content, tr::phrase<> text) { @@ -234,53 +223,6 @@ void InnerWidget::fill() { } { AddHeader(container, tr::lng_bot_earn_balance_title); - Ui::AddSkip(container); - - const auto labels = container->add( - object_ptr>( - container, - object_ptr(container)))->entity(); - - const auto majorLabel = Ui::CreateChild( - labels, - rpl::duplicate(availableBalanceValue) | rpl::map(valueToString), - st::channelEarnBalanceMajorLabel); - const auto icon = Ui::CreateSingleStarWidget( - labels, - majorLabel->height()); - majorLabel->setAttribute(Qt::WA_TransparentForMouseEvents); - majorLabel->sizeValue( - ) | rpl::start_with_next([=](const QSize &majorSize) { - const auto skip = st::channelEarnBalanceMinorLabelSkip; - labels->resize( - majorSize.width() + icon->width() + skip, - majorSize.height()); - majorLabel->moveToLeft(icon->width() + skip, 0); - }, labels->lifetime()); - Ui::ToggleChildrenVisibility(labels, true); - - Ui::AddSkip(container); - container->add( - object_ptr>( - container, - object_ptr( - container, - rpl::duplicate( - availableBalanceValue - ) | rpl::map([=](uint64 v) { - return v ? ToUsd(v, multiplier) : QString(); - }), - st::channelEarnOverviewSubMinorLabel))); - - Ui::AddSkip(container); - - const auto input = Ui::AddInputFieldForCredits( - container, - rpl::duplicate(availableBalanceValue)); - - Ui::AddSkip(container); - Ui::AddSkip(container); - auto dateValue = rpl::single( data.nextWithdrawalAt ) | rpl::then( @@ -288,220 +230,18 @@ void InnerWidget::fill() { return _state.nextWithdrawalAt; }) ); - auto lockedValue = rpl::duplicate( - dateValue - ) | rpl::map([=](const QDateTime &dt) { - return !dt.isNull() || (!_state.isWithdrawalEnabled); - }); - - const auto &stButton = st::defaultActiveButton; - const auto button = container->add( - object_ptr( - container, - rpl::never(), - stButton), - st::boxRowPadding); - - rpl::duplicate( - lockedValue - ) | rpl::start_with_next([=](bool v) { - button->setAttribute(Qt::WA_TransparentForMouseEvents, v); - }, button->lifetime()); - - const auto label = Ui::CreateChild( - button, - tr::lng_channel_earn_balance_button(tr::now), - st::channelEarnSemiboldLabel); - const auto processInputChange = [&] { - const auto buttonEmoji = Ui::Text::SingleCustomEmoji( - session->data().customEmojiManager().registerInternalEmoji( - st::settingsPremiumIconStar, - { 0, -st::moderateBoxExpandInnerSkip, 0, 0 }, - true)); - const auto context = Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = [=] { label->update(); }, - }; - const auto process = [=] { - const auto amount = input->getLastText().toDouble(); - if (amount >= _state.availableBalance) { - label->setText( - tr::lng_bot_earn_balance_button_all(tr::now)); - } else { - label->setMarkedText( - tr::lng_bot_earn_balance_button( - tr::now, - lt_count, - amount, - lt_emoji, - buttonEmoji, - Ui::Text::RichLangValue), - context); - } - }; - QObject::connect(input, &Ui::MaskedInputField::changed, process); - process(); - return process; - }(); - label->setTextColorOverride(stButton.textFg->c); - label->setAttribute(Qt::WA_TransparentForMouseEvents); - rpl::combine( - rpl::duplicate(lockedValue), - button->sizeValue(), - label->sizeValue() - ) | rpl::start_with_next([=](bool v, const QSize &b, const QSize &l) { - label->moveToLeft( - (b.width() - l.width()) / 2, - (v ? -10 : 1) * (b.height() - l.height()) / 2); - }, label->lifetime()); - - const auto lockedColor = anim::with_alpha(stButton.textFg->c, .5); - const auto lockedLabelTop = Ui::CreateChild( - button, - tr::lng_bot_earn_balance_button_locked(), - st::botEarnLockedButtonLabel); - lockedLabelTop->setTextColorOverride(lockedColor); - lockedLabelTop->setAttribute(Qt::WA_TransparentForMouseEvents); - const auto lockedLabelBottom = Ui::CreateChild( - button, - QString(), - st::botEarnLockedButtonLabel); - lockedLabelBottom->setTextColorOverride(lockedColor); - lockedLabelBottom->setAttribute(Qt::WA_TransparentForMouseEvents); - rpl::combine( - rpl::duplicate(lockedValue), - button->sizeValue(), - lockedLabelTop->sizeValue(), - lockedLabelBottom->sizeValue() - ) | rpl::start_with_next([=]( - bool locked, - const QSize &b, - const QSize &top, - const QSize &bottom) { - const auto factor = locked ? 1 : -10; - const auto sumHeight = top.height() + bottom.height(); - lockedLabelTop->moveToLeft( - (b.width() - top.width()) / 2, - factor * (b.height() - sumHeight) / 2); - lockedLabelBottom->moveToLeft( - (b.width() - bottom.width()) / 2, - factor * ((b.height() - sumHeight) / 2 + top.height())); - }, lockedLabelTop->lifetime()); - - const auto dateUpdateLifetime - = lockedLabelBottom->lifetime().make_state(); - std::move( - dateValue - ) | rpl::start_with_next([=](const QDateTime &dt) { - dateUpdateLifetime->destroy(); - if (dt.isNull()) { - return; - } - constexpr auto kDateUpdateInterval = crl::time(250); - const auto was = base::unixtime::serialize(dt); - - const auto context = Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = [=] { lockedLabelBottom->update(); }, - }; - const auto emoji = Ui::Text::SingleCustomEmoji( - session->data().customEmojiManager().registerInternalEmoji( - st::chatSimilarLockedIcon, - st::botEarnButtonLockMargins, - true)); - - rpl::single( - rpl::empty - ) | rpl::then( - base::timer_each(kDateUpdateInterval) - ) | rpl::start_with_next([=] { - const auto secondsDifference = std::max( - was - base::unixtime::now() - 1, - 0); - const auto hours = secondsDifference / 3600; - const auto minutes = (secondsDifference % 3600) / 60; - const auto seconds = secondsDifference % 60; - constexpr auto kZero = QChar('0'); - const auto formatted = (hours > 0) - ? (u"%1:%2:%3"_q) - .arg(hours, 2, 10, kZero) - .arg(minutes, 2, 10, kZero) - .arg(seconds, 2, 10, kZero) - : (u"%1:%2"_q) - .arg(minutes, 2, 10, kZero) - .arg(seconds, 2, 10, kZero); - lockedLabelBottom->setMarkedText( - base::duplicate(emoji).append(formatted), - context); - }, *dateUpdateLifetime); - }, lockedLabelBottom->lifetime()); - - Api::HandleWithdrawalButton( - Api::RewardReceiver{ - .creditsReceiver = _peer, - .creditsAmount = [=, show = _controller->uiShow()] { - const auto amount = input->getLastText().toULongLong(); - const auto min = float64(WithdrawalMin(session)); - if (amount < min) { - auto text = tr::lng_bot_earn_credits_out_minimal( - tr::now, - lt_link, - Ui::Text::Link( - tr::lng_bot_earn_credits_out_minimal_link( - tr::now, - lt_count, - min), - u"internal:"_q), - Ui::Text::RichLangValue); - show->showToast(Ui::Toast::Config{ - .text = std::move(text), - .filter = [=](const auto ...) { - input->setText(QString::number(min)); - processInputChange(); - return true; - }, - }); - return 0ULL; - } - return amount; - }, - }, - button, - _controller->uiShow()); - Ui::ToggleChildrenVisibility(button, true); - - Ui::AddSkip(container); - Ui::AddSkip(container); - - const auto arrow = Ui::Text::SingleCustomEmoji( - session->data().customEmojiManager().registerInternalEmoji( - st::topicButtonArrow, - st::channelEarnLearnArrowMargins, - false)); - auto about = Ui::CreateLabelWithCustomEmoji( + ::Settings::AddWithdrawalWidget( container, - tr::lng_bot_earn_learn_credits_out_about( - lt_link, - tr::lng_channel_earn_about_link( - lt_emoji, - rpl::single(arrow), - Ui::Text::RichLangValue - ) | rpl::map([](TextWithEntities text) { - return Ui::Text::Link( - std::move(text), - tr::lng_bot_earn_balance_about_url(tr::now)); - }), - Ui::Text::RichLangValue), - { .session = session }, - st::boxDividerLabel); - Ui::AddSkip(container); - container->add(object_ptr( - container, - std::move(about), - st::defaultBoxDividerLabelPadding, - RectPart::Top | RectPart::Bottom)); - - Ui::AddSkip(container); + _controller->parentController(), + _peer, + rpl::duplicate(availableBalanceValue), + rpl::duplicate(dateValue), + std::move(dateValue) | rpl::map([=](const QDateTime &dt) { + return !dt.isNull() || (!_state.isWithdrawalEnabled); + }), + rpl::duplicate(availableBalanceValue) | rpl::map([=](uint64 v) { + return v ? ToUsd(v, multiplier) : QString(); + })); } fillHistory(); diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 0aa341dd2..4b4e5de00 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -8,20 +8,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_credits_graphics.h" #include "api/api_credits.h" +#include "api/api_earn.h" +#include "base/timer_rpl.h" +#include "base/unixtime.h" #include "boxes/gift_premium_box.h" #include "core/click_handler_types.h" +#include "core/ui_integration.h" #include "data/data_file_origin.h" #include "data/data_photo_media.h" #include "data/data_session.h" #include "data/data_user.h" +#include "data/stickers/data_custom_emoji.h" #include "info/settings/info_settings_widget.h" // SectionCustomTopBarData. #include "info/statistics/info_statistics_list_controllers.h" #include "lang/lang_keys.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "payments/payments_checkout_process.h" #include "payments/payments_form.h" #include "settings/settings_common_session.h" -#include "settings/settings_credits_graphics.h" #include "statistics/widgets/chart_header_widget.h" #include "ui/controls/userpic_button.h" #include "ui/effects/credits_graphics.h" @@ -33,9 +38,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rect.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" #include "ui/widgets/discrete_sliders.h" +#include "ui/widgets/fields/number_input.h" +#include "ui/widgets/label_with_custom_emoji.h" #include "ui/widgets/labels.h" #include "ui/widgets/tooltip.h" #include "ui/wrap/fade_wrap.h" @@ -43,6 +51,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" +#include "styles/style_channel_earn.h" +#include "styles/style_chat.h" #include "styles/style_credits.h" #include "styles/style_giveaway.h" #include "styles/style_info.h" @@ -68,6 +78,11 @@ namespace { return XXH64(string.data(), string.size() * sizeof(ushort), 0); } +[[nodiscard]] int WithdrawalMin(not_null session) { + const auto key = u"stars_revenue_withdrawal_min"_q; + return session->appConfig().get(key, 1000); +} + class Balance final : public Ui::RpWidget , public Ui::AbstractTooltipShower { @@ -643,4 +658,272 @@ void SmallBalanceBox( } } +void AddWithdrawalWidget( + not_null container, + not_null controller, + not_null peer, + rpl::producer availableBalanceValue, + rpl::producer dateValue, + rpl::producer lockedValue, + rpl::producer usdValue) { + Ui::AddSkip(container); + + const auto labels = container->add( + object_ptr>( + container, + object_ptr(container)))->entity(); + + const auto majorLabel = Ui::CreateChild( + labels, + rpl::duplicate(availableBalanceValue) | rpl::map([](uint64 v) { + return Lang::FormatCountDecimal(v); + }), + st::channelEarnBalanceMajorLabel); + const auto icon = Ui::CreateSingleStarWidget( + labels, + majorLabel->height()); + majorLabel->setAttribute(Qt::WA_TransparentForMouseEvents); + majorLabel->sizeValue( + ) | rpl::start_with_next([=](const QSize &majorSize) { + const auto skip = st::channelEarnBalanceMinorLabelSkip; + labels->resize( + majorSize.width() + icon->width() + skip, + majorSize.height()); + majorLabel->moveToLeft(icon->width() + skip, 0); + }, labels->lifetime()); + Ui::ToggleChildrenVisibility(labels, true); + + Ui::AddSkip(container); + container->add( + object_ptr>( + container, + object_ptr( + container, + std::move(usdValue), + st::channelEarnOverviewSubMinorLabel))); + + Ui::AddSkip(container); + + const auto input = Ui::AddInputFieldForCredits( + container, + rpl::duplicate(availableBalanceValue)); + + Ui::AddSkip(container); + Ui::AddSkip(container); + + const auto &stButton = st::defaultActiveButton; + const auto button = container->add( + object_ptr( + container, + rpl::never(), + stButton), + st::boxRowPadding); + + rpl::duplicate( + lockedValue + ) | rpl::start_with_next([=](bool v) { + button->setAttribute(Qt::WA_TransparentForMouseEvents, v); + }, button->lifetime()); + + const auto session = &controller->session(); + + const auto label = Ui::CreateChild( + button, + tr::lng_channel_earn_balance_button(tr::now), + st::channelEarnSemiboldLabel); + const auto processInputChange = [&] { + const auto buttonEmoji = Ui::Text::SingleCustomEmoji( + session->data().customEmojiManager().registerInternalEmoji( + st::settingsPremiumIconStar, + { 0, -st::moderateBoxExpandInnerSkip, 0, 0 }, + true)); + const auto context = Core::MarkedTextContext{ + .session = session, + .customEmojiRepaint = [=] { label->update(); }, + }; + using Balance = rpl::variable; + const auto currentBalance = input->lifetime().make_state( + rpl::duplicate(availableBalanceValue)); + const auto process = [=] { + const auto amount = input->getLastText().toDouble(); + if (amount >= currentBalance->current()) { + label->setText( + tr::lng_bot_earn_balance_button_all(tr::now)); + } else { + label->setMarkedText( + tr::lng_bot_earn_balance_button( + tr::now, + lt_count, + amount, + lt_emoji, + buttonEmoji, + Ui::Text::RichLangValue), + context); + } + }; + QObject::connect(input, &Ui::MaskedInputField::changed, process); + process(); + return process; + }(); + label->setTextColorOverride(stButton.textFg->c); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + rpl::combine( + rpl::duplicate(lockedValue), + button->sizeValue(), + label->sizeValue() + ) | rpl::start_with_next([=](bool v, const QSize &b, const QSize &l) { + label->moveToLeft( + (b.width() - l.width()) / 2, + (v ? -10 : 1) * (b.height() - l.height()) / 2); + }, label->lifetime()); + + const auto lockedColor = anim::with_alpha(stButton.textFg->c, .5); + const auto lockedLabelTop = Ui::CreateChild( + button, + tr::lng_bot_earn_balance_button_locked(), + st::botEarnLockedButtonLabel); + lockedLabelTop->setTextColorOverride(lockedColor); + lockedLabelTop->setAttribute(Qt::WA_TransparentForMouseEvents); + const auto lockedLabelBottom = Ui::CreateChild( + button, + QString(), + st::botEarnLockedButtonLabel); + lockedLabelBottom->setTextColorOverride(lockedColor); + lockedLabelBottom->setAttribute(Qt::WA_TransparentForMouseEvents); + rpl::combine( + rpl::duplicate(lockedValue), + button->sizeValue(), + lockedLabelTop->sizeValue(), + lockedLabelBottom->sizeValue() + ) | rpl::start_with_next([=]( + bool locked, + const QSize &b, + const QSize &top, + const QSize &bottom) { + const auto factor = locked ? 1 : -10; + const auto sumHeight = top.height() + bottom.height(); + lockedLabelTop->moveToLeft( + (b.width() - top.width()) / 2, + factor * (b.height() - sumHeight) / 2); + lockedLabelBottom->moveToLeft( + (b.width() - bottom.width()) / 2, + factor * ((b.height() - sumHeight) / 2 + top.height())); + }, lockedLabelTop->lifetime()); + + const auto dateUpdateLifetime + = lockedLabelBottom->lifetime().make_state(); + std::move( + dateValue + ) | rpl::start_with_next([=](const QDateTime &dt) { + dateUpdateLifetime->destroy(); + if (dt.isNull()) { + return; + } + constexpr auto kDateUpdateInterval = crl::time(250); + const auto was = base::unixtime::serialize(dt); + + const auto context = Core::MarkedTextContext{ + .session = session, + .customEmojiRepaint = [=] { lockedLabelBottom->update(); }, + }; + const auto emoji = Ui::Text::SingleCustomEmoji( + session->data().customEmojiManager().registerInternalEmoji( + st::chatSimilarLockedIcon, + st::botEarnButtonLockMargins, + true)); + + rpl::single( + rpl::empty + ) | rpl::then( + base::timer_each(kDateUpdateInterval) + ) | rpl::start_with_next([=] { + const auto secondsDifference = std::max( + was - base::unixtime::now() - 1, + 0); + const auto hours = secondsDifference / 3600; + const auto minutes = (secondsDifference % 3600) / 60; + const auto seconds = secondsDifference % 60; + constexpr auto kZero = QChar('0'); + const auto formatted = (hours > 0) + ? (u"%1:%2:%3"_q) + .arg(hours, 2, 10, kZero) + .arg(minutes, 2, 10, kZero) + .arg(seconds, 2, 10, kZero) + : (u"%1:%2"_q) + .arg(minutes, 2, 10, kZero) + .arg(seconds, 2, 10, kZero); + lockedLabelBottom->setMarkedText( + base::duplicate(emoji).append(formatted), + context); + }, *dateUpdateLifetime); + }, lockedLabelBottom->lifetime()); + + Api::HandleWithdrawalButton( + Api::RewardReceiver{ + .creditsReceiver = peer, + .creditsAmount = [=, show = controller->uiShow()] { + const auto amount = input->getLastText().toULongLong(); + const auto min = float64(WithdrawalMin(session)); + if (amount < min) { + auto text = tr::lng_bot_earn_credits_out_minimal( + tr::now, + lt_link, + Ui::Text::Link( + tr::lng_bot_earn_credits_out_minimal_link( + tr::now, + lt_count, + min), + u"internal:"_q), + Ui::Text::RichLangValue); + show->showToast(Ui::Toast::Config{ + .text = std::move(text), + .filter = [=](const auto ...) { + input->setText(QString::number(min)); + processInputChange(); + return true; + }, + }); + return 0ULL; + } + return amount; + }, + }, + button, + controller->uiShow()); + Ui::ToggleChildrenVisibility(button, true); + + Ui::AddSkip(container); + Ui::AddSkip(container); + + const auto arrow = Ui::Text::SingleCustomEmoji( + session->data().customEmojiManager().registerInternalEmoji( + st::topicButtonArrow, + st::channelEarnLearnArrowMargins, + false)); + auto about = Ui::CreateLabelWithCustomEmoji( + container, + tr::lng_bot_earn_learn_credits_out_about( + lt_link, + tr::lng_channel_earn_about_link( + lt_emoji, + rpl::single(arrow), + Ui::Text::RichLangValue + ) | rpl::map([](TextWithEntities text) { + return Ui::Text::Link( + std::move(text), + tr::lng_bot_earn_balance_about_url(tr::now)); + }), + Ui::Text::RichLangValue), + { .session = session }, + st::boxDividerLabel); + Ui::AddSkip(container); + container->add(object_ptr( + container, + std::move(about), + st::defaultBoxDividerLabelPadding, + RectPart::Top | RectPart::Bottom)); + + Ui::AddSkip(container); +} + } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index d191187b3..ae256d886 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -39,6 +39,15 @@ void FillCreditOptions( rpl::producer balanceValue, bool rightAlign); +void AddWithdrawalWidget( + not_null container, + not_null controller, + not_null peer, + rpl::producer availableBalanceValue, + rpl::producer dateValue, + rpl::producer lockedValue, + rpl::producer usdValue); + void ReceiptCreditsBox( not_null box, not_null controller, From b6748263921f70d433ad0a4c9fb6fadf4c35b8b1 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 24 Jun 2024 02:31:09 +0300 Subject: [PATCH 102/142] Added special widget for credits input to section of channel earn. --- .../earn/info_earn_inner_widget.cpp | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp index 333cb47ff..47a921a89 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp @@ -378,6 +378,12 @@ void InnerWidget::fill() { //constexpr auto kApproximately = QChar(0x2248); const auto multiplier = data.usdRate; + const auto creditsToUsdMap = [=](EarnInt c) { + const auto creditsMultiplier = _state.creditsEarn.usdRate + * Data::kEarnMultiplier; + return c ? ToUsd(c, creditsMultiplier) : QString(); + }; + constexpr auto kNonInteractivePeriod = 1717200000; const auto nonInteractive = base::unixtime::now() < kNonInteractivePeriod; @@ -724,15 +730,9 @@ void InnerWidget::fill() { const auto icon = Ui::CreateSingleStarWidget( line, creditsLabel->height()); - const auto creditsMultiplier = creditsData.usdRate - * Data::kEarnMultiplier; const auto creditsSecondLabel = Ui::CreateChild( line, - rpl::duplicate( - creditsValue - ) | rpl::map([creditsMultiplier](EarnInt c) { - return c ? ToUsd(c, creditsMultiplier) : QString(); - }), + rpl::duplicate(creditsValue) | rpl::map(creditsToUsdMap), st::channelEarnOverviewSubMinorLabel); rpl::combine( line->widthValue(), @@ -915,6 +915,36 @@ void InnerWidget::fill() { : tr::lng_channel_earn_balance_about_temp); Ui::AddSkip(container); } + if (creditsData.availableBalance > 0) { + AddHeader(container, tr::lng_bot_earn_balance_title); + auto availableBalanceValue = rpl::single( + creditsData.availableBalance + ) | rpl::then( + _stateUpdated.events() | rpl::map([=] { + return _state.creditsEarn.availableBalance; + }) + ); + auto dateValue = rpl::single( + creditsData.nextWithdrawalAt + ) | rpl::then( + _stateUpdated.events() | rpl::map([=] { + return _state.creditsEarn.nextWithdrawalAt; + }) + ); + ::Settings::AddWithdrawalWidget( + container, + _controller->parentController(), + _peer, + rpl::duplicate(availableBalanceValue), + rpl::duplicate(dateValue), + std::move(dateValue) | rpl::map([=](const QDateTime &dt) { + return !dt.isNull() + || (!_state.creditsEarn.isWithdrawalEnabled); + }), + rpl::duplicate( + availableBalanceValue + ) | rpl::map(creditsToUsdMap)); + } const auto sectionIndex = container->lifetime().make_state(0); const auto rebuildLists = [=]( From d9caf15d1d5e39892cdde1852e508c7f3c49d300 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 24 Jun 2024 03:07:22 +0300 Subject: [PATCH 103/142] Added initial ability to reinvest existing credits for channels. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/api/api_credits.cpp | 52 ++++++++++++------- Telegram/SourceFiles/data/data_bot_earn.h | 1 + .../info/bot/earn/info_earn_inner_widget.cpp | 7 +++ .../earn/info_earn_inner_widget.cpp | 7 +++ .../settings/settings_credits_graphics.cpp | 41 +++++++++++++-- .../settings/settings_credits_graphics.h | 1 + 7 files changed, 86 insertions(+), 24 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 2ca7529f3..f18bd0155 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -5229,6 +5229,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_earn_balance_button#other" = "Withdraw {emoji} {count} via Fragment"; "lng_bot_earn_balance_button_all" = "Withdraw all stars via Fragment"; "lng_bot_earn_balance_button_locked" = "Withdraw via Fragment"; +"lng_bot_earn_balance_button_buy_ads" = "Buy Ads"; "lng_bot_earn_learn_credits_out_about" = "You can withdraw Stars using Fragment, or use Stars to advertise your bot. {link}"; "lng_bot_earn_out_ph" = "Enter amount to withdraw"; "lng_bot_earn_balance_password_title" = "Two-step verification"; diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 50544f77e..7e3ebe9b8 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -237,27 +237,41 @@ rpl::producer CreditsEarnStatistics::request() { return [=](auto consumer) { auto lifetime = rpl::lifetime(); - makeRequest(MTPpayments_GetStarsRevenueStats( - MTP_flags(0), - (_isUser ? user()->input : channel()->input) - )).done([=](const MTPpayments_StarsRevenueStats &result) { - const auto &data = result.data(); - const auto &status = data.vstatus().data(); - _data = Data::CreditsEarnStatistics{ - .revenueGraph = StatisticalGraphFromTL(data.vrevenue_graph()), - .currentBalance = status.vcurrent_balance().v, - .availableBalance = status.vavailable_balance().v, - .overallRevenue = status.voverall_revenue().v, - .usdRate = data.vusd_rate().v, - .isWithdrawalEnabled = status.is_withdrawal_enabled(), - .nextWithdrawalAt = status.vnext_withdrawal_at() - ? base::unixtime::parse(status.vnext_withdrawal_at()->v) - : QDateTime(), - }; + const auto finish = [=](const QString &url) { + makeRequest(MTPpayments_GetStarsRevenueStats( + MTP_flags(0), + (_isUser ? user()->input : channel()->input) + )).done([=](const MTPpayments_StarsRevenueStats &result) { + const auto &data = result.data(); + const auto &status = data.vstatus().data(); + _data = Data::CreditsEarnStatistics{ + .revenueGraph = StatisticalGraphFromTL( + data.vrevenue_graph()), + .currentBalance = status.vcurrent_balance().v, + .availableBalance = status.vavailable_balance().v, + .overallRevenue = status.voverall_revenue().v, + .usdRate = data.vusd_rate().v, + .isWithdrawalEnabled = status.is_withdrawal_enabled(), + .nextWithdrawalAt = status.vnext_withdrawal_at() + ? base::unixtime::parse( + status.vnext_withdrawal_at()->v) + : QDateTime(), + .buyAdsUrl = url, + }; - consumer.put_done(); + consumer.put_done(); + }).fail([=](const MTP::Error &error) { + consumer.put_error_copy(error.type()); + }).send(); + }; + + makeRequest( + MTPpayments_GetStarsRevenueAdsAccountUrl( + (_isUser ? user()->input : channel()->input)) + ).done([=](const MTPpayments_StarsRevenueAdsAccountUrl &result) { + finish(qs(result.data().vurl())); }).fail([=](const MTP::Error &error) { - consumer.put_error_copy(error.type()); + finish({}); }).send(); return lifetime; diff --git a/Telegram/SourceFiles/data/data_bot_earn.h b/Telegram/SourceFiles/data/data_bot_earn.h index 080feb174..c82d58a74 100644 --- a/Telegram/SourceFiles/data/data_bot_earn.h +++ b/Telegram/SourceFiles/data/data_bot_earn.h @@ -26,6 +26,7 @@ struct CreditsEarnStatistics final { float64 usdRate = 0.; bool isWithdrawalEnabled = false; QDateTime nextWithdrawalAt; + QString buyAdsUrl; }; } // namespace Data diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp index b6d37dc1e..bc6f90e1d 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp @@ -234,6 +234,13 @@ void InnerWidget::fill() { container, _controller->parentController(), _peer, + rpl::single( + data.buyAdsUrl + ) | rpl::then( + _stateUpdated.events() | rpl::map([=] { + return _state.buyAdsUrl; + }) + ), rpl::duplicate(availableBalanceValue), rpl::duplicate(dateValue), std::move(dateValue) | rpl::map([=](const QDateTime &dt) { diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp index 47a921a89..e58149319 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp @@ -935,6 +935,13 @@ void InnerWidget::fill() { container, _controller->parentController(), _peer, + rpl::single( + creditsData.buyAdsUrl + ) | rpl::then( + _stateUpdated.events() | rpl::map([=] { + return _state.creditsEarn.buyAdsUrl; + }) + ), rpl::duplicate(availableBalanceValue), rpl::duplicate(dateValue), std::move(dateValue) | rpl::map([=](const QDateTime &dt) { diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 4b4e5de00..b5c890309 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/click_handler_types.h" #include "core/ui_integration.h" #include "data/data_file_origin.h" +#include "core/click_handler_types.h" // UrlClickHandler #include "data/data_photo_media.h" #include "data/data_session.h" #include "data/data_user.h" @@ -662,6 +663,7 @@ void AddWithdrawalWidget( not_null container, not_null controller, not_null peer, + rpl::producer secondButtonUrl, rpl::producer availableBalanceValue, rpl::producer dateValue, rpl::producer lockedValue, @@ -712,13 +714,42 @@ void AddWithdrawalWidget( Ui::AddSkip(container); const auto &stButton = st::defaultActiveButton; - const auto button = container->add( - object_ptr( - container, - rpl::never(), - stButton), + const auto buttonsContainer = container->add( + Ui::CreateSkipWidget(container, stButton.height), st::boxRowPadding); + const auto button = Ui::CreateChild( + buttonsContainer, + rpl::never(), + stButton); + + const auto buttonCredits = Ui::CreateChild( + buttonsContainer, + tr::lng_bot_earn_balance_button_buy_ads(), + stButton); + buttonCredits->setTextTransform( + Ui::RoundButton::TextTransform::NoTransform); + + Ui::ToggleChildrenVisibility(buttonsContainer, true); + + rpl::combine( + std::move(secondButtonUrl), + buttonsContainer->sizeValue() + ) | rpl::start_with_next([=](const QString &url, const QSize &size) { + if (url.isEmpty()) { + button->resize(size.width(), size.height()); + buttonCredits->resize(0, 0); + } else { + const auto w = size.width() - st::boxRowPadding.left() / 2; + button->resize(w / 2, size.height()); + buttonCredits->resize(w / 2, size.height()); + buttonCredits->moveToRight(0, 0); + buttonCredits->setClickedCallback([=] { + UrlClickHandler::Open(url); + }); + } + }, buttonsContainer->lifetime()); + rpl::duplicate( lockedValue ) | rpl::start_with_next([=](bool v) { diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index ae256d886..8119213b7 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -43,6 +43,7 @@ void AddWithdrawalWidget( not_null container, not_null controller, not_null peer, + rpl::producer secondButtonUrl, rpl::producer availableBalanceValue, rpl::producer dateValue, rpl::producer lockedValue, From 0fc2df8eec24c7eab97d6a28ea81b69018ae1639 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 24 Jun 2024 05:03:23 +0300 Subject: [PATCH 104/142] Added transaction link and date to details of credits history entries. --- Telegram/Resources/langs/lang.strings | 2 ++ Telegram/SourceFiles/api/api_credits.cpp | 4 ++-- Telegram/SourceFiles/boxes/gift_premium_box.cpp | 13 +++++++++++++ Telegram/SourceFiles/data/data_credits.h | 4 ++-- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f18bd0155..52c66b3d4 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2353,6 +2353,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_box_history_entry_ads" = "Ads Platform"; "lng_credits_box_history_entry_id" = "Transaction ID"; "lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard."; +"lng_credits_box_history_entry_success_date" = "Transaction date"; +"lng_credits_box_history_entry_success_url" = "Transaction link"; "lng_credits_box_history_entry_media" = "Media"; "lng_credits_box_history_entry_about" = "You can dispute this transaction {link}."; "lng_credits_box_history_entry_about_link" = "here"; diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 7e3ebe9b8..4473b78cd 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -74,10 +74,10 @@ constexpr auto kTransactionsLimit = 100; .refunded = tl.data().is_refund(), .pending = tl.data().is_pending(), .failed = tl.data().is_failed(), - .finishDate = tl.data().vtransaction_date() + .successDate = tl.data().vtransaction_date() ? base::unixtime::parse(tl.data().vtransaction_date()->v) : QDateTime(), - .finishUrl = qs(tl.data().vtransaction_url().value_or_empty()), + .successLink = qs(tl.data().vtransaction_url().value_or_empty()), .in = (!isBot || tl.data().is_refund()) && !tl.data().is_pending() && !tl.data().is_failed(), diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index c7e22969f..972a7a5f4 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -1730,4 +1730,17 @@ void AddCreditsHistoryEntryTable( tr::lng_gift_link_label_date(), rpl::single(Ui::Text::WithEntities(langDateTime(entry.date)))); } + if (!entry.successDate.isNull()) { + AddTableRow( + table, + tr::lng_credits_box_history_entry_success_date(), + rpl::single(Ui::Text::WithEntities(langDateTime(entry.date)))); + } + if (!entry.successLink.isEmpty()) { + AddTableRow( + table, + tr::lng_credits_box_history_entry_success_url(), + rpl::single( + Ui::Text::Link(entry.successLink, entry.successLink))); + } } diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index 9a12b3ef4..74fe1c4eb 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -42,8 +42,8 @@ struct CreditsHistoryEntry final { bool refunded = false; bool pending = false; bool failed = false; - QDateTime finishDate; - QString finishUrl; + QDateTime successDate; + QString successLink; bool in = false; }; From 54d0290ba237d08701ef9d8215c57acfe05a04e9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 24 Jun 2024 05:52:34 +0300 Subject: [PATCH 105/142] Added warning to channel earn section when user has no cloud password. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/api/api_earn.cpp | 58 ++++++++++++++++++++------- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 52c66b3d4..fb8aa23e7 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -5218,6 +5218,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_channel_earn_chart_overriden_detail_usd" = "Revenue in USD"; "lng_channel_earn_currency_history" = "TON Transactions"; "lng_channel_earn_credits_history" = "Stars Transactions"; +"lng_channel_earn_out_check_password_about" = "You can withdraw only if you have:"; "lng_bot_earn_title" = "Stars Balance"; "lng_bot_earn_chart_revenue" = "Revenue"; diff --git a/Telegram/SourceFiles/api/api_earn.cpp b/Telegram/SourceFiles/api/api_earn.cpp index 4997fb0ec..f05e5f618 100644 --- a/Telegram/SourceFiles/api/api_earn.cpp +++ b/Telegram/SourceFiles/api/api_earn.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_cloud_password.h" #include "apiwrap.h" +#include "ui/layers/generic_box.h" #include "boxes/passcode_box.h" #include "data/data_channel.h" #include "data/data_session.h" @@ -50,8 +51,11 @@ void HandleWithdrawalButton( const auto state = button->lifetime().make_state(); const auto session = (channel ? &channel->session() : &peer->session()); + using ChannelOutUrl = MTPstats_BroadcastRevenueWithdrawalUrl; + using CreditsOutUrl = MTPpayments_StarsRevenueWithdrawalUrl; + session->api().cloudPassword().reload(); - button->setClickedCallback([=] { + const auto processOut = [=] { if (state->loading) { return; } @@ -84,37 +88,63 @@ void HandleWithdrawalButton( } } }; - const auto fail = [=](const QString &error) { - show->showToast(error); + const auto fail = [=](const MTP::Error &error) { + show->showToast(error.type()); }; if (channel) { session->api().request( MTPstats_GetBroadcastRevenueWithdrawalUrl( channel->inputChannel, result.result - )).done([=]( - const MTPstats_BroadcastRevenueWithdrawalUrl &r) { + )).done([=](const ChannelOutUrl &r) { done(qs(r.data().vurl())); - }).fail([=](const MTP::Error &error) { - fail(error.type()); - }).send(); + }).fail(fail).send(); } else if (peer) { session->api().request( MTPpayments_GetStarsRevenueWithdrawalUrl( peer->input, MTP_long(receiver.creditsAmount()), result.result - )).done([=]( - const MTPpayments_StarsRevenueWithdrawalUrl &r) { + )).done([=](const CreditsOutUrl &r) { done(qs(r.data().vurl())); - }).fail([=](const MTP::Error &error) { - fail(error.type()); - }).send(); + }).fail(fail).send(); } }); show->show(Box(session, fields)); }); - + }; + button->setClickedCallback([=] { + if (state->loading) { + return; + } + const auto fail = [=](const MTP::Error &error) { + auto box = PrePasswordErrorBox( + error.type(), + session, + TextWithEntities{ + tr::lng_channel_earn_out_check_password_about(tr::now), + }); + if (box) { + show->show(std::move(box)); + state->loading = false; + } else { + processOut(); + } + }; + if (channel) { + session->api().request( + MTPstats_GetBroadcastRevenueWithdrawalUrl( + channel->inputChannel, + MTP_inputCheckPasswordEmpty() + )).fail(fail).send(); + } else if (peer) { + session->api().request( + MTPpayments_GetStarsRevenueWithdrawalUrl( + peer->input, + MTP_long(std::numeric_limits::max()), + MTP_inputCheckPasswordEmpty() + )).fail(fail).send(); + } }); } From 157d5c743bdd49e587a1b6b6290bdbe4a0d566b2 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 24 Jun 2024 06:17:08 +0300 Subject: [PATCH 106/142] Renamed file for data of credits earn statistics. --- Telegram/SourceFiles/api/api_credits.h | 2 +- .../SourceFiles/data/{data_bot_earn.h => data_credits_earn.h} | 0 Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h | 2 +- Telegram/SourceFiles/info/bot/earn/info_earn_widget.h | 2 +- .../SourceFiles/info/channel_statistics/earn/info_earn_widget.h | 2 +- Telegram/cmake/td_ui.cmake | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename Telegram/SourceFiles/data/{data_bot_earn.h => data_credits_earn.h} (100%) diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h index ae2e0984d..e63380734 100644 --- a/Telegram/SourceFiles/api/api_credits.h +++ b/Telegram/SourceFiles/api/api_credits.h @@ -8,8 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "api/api_statistics_sender.h" -#include "data/data_bot_earn.h" #include "data/data_credits.h" +#include "data/data_credits_earn.h" #include "mtproto/sender.h" namespace Main { diff --git a/Telegram/SourceFiles/data/data_bot_earn.h b/Telegram/SourceFiles/data/data_credits_earn.h similarity index 100% rename from Telegram/SourceFiles/data/data_bot_earn.h rename to Telegram/SourceFiles/data/data_credits_earn.h diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h index 60207334d..7157c4f02 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.h @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "data/data_bot_earn.h" +#include "data/data_credits_earn.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/vertical_layout.h" diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_widget.h b/Telegram/SourceFiles/info/bot/earn/info_earn_widget.h index 12397750e..37ab50e94 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_widget.h +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_widget.h @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "data/data_bot_earn.h" +#include "data/data_credits_earn.h" #include "info/info_content_widget.h" namespace Info::BotEarn { diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_widget.h b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_widget.h index 5979c5c37..becd834c6 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_widget.h +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_widget.h @@ -7,9 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "data/data_bot_earn.h" #include "data/data_channel_earn.h" #include "data/data_credits.h" +#include "data/data_credits_earn.h" #include "info/info_content_widget.h" namespace Info::ChannelEarn { diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index f5cd963c5..3555c9218 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -80,9 +80,9 @@ PRIVATE data/data_birthday.cpp data/data_birthday.h - data/data_bot_earn.h data/data_channel_earn.h data/data_credits.h + data/data_credits_earn.h data/data_statistics_chart.cpp data/data_statistics_chart.h data/data_subscription_option.h From 68b9a8bc6aa0e78f1d544e4294e29be838b460a6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 24 Jun 2024 11:09:23 +0400 Subject: [PATCH 107/142] Fix file reference refresh for paid media. --- Telegram/SourceFiles/data/data_file_origin.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Telegram/SourceFiles/data/data_file_origin.cpp b/Telegram/SourceFiles/data/data_file_origin.cpp index c97d98a1a..76838edc3 100644 --- a/Telegram/SourceFiles/data/data_file_origin.cpp +++ b/Telegram/SourceFiles/data/data_file_origin.cpp @@ -76,6 +76,12 @@ struct FileReferenceAccumulator { }, [](const auto &data) { }); } + void push(const MTPMessageExtendedMedia &data) { + data.match([&](const MTPDmessageExtendedMediaPreview &data) { + }, [&](const MTPDmessageExtendedMedia &data) { + push(data.vmedia()); + }); + } void push(const MTPMessageMedia &data) { data.match([&](const MTPDmessageMediaPhoto &data) { push(data.vphoto()); @@ -85,6 +91,10 @@ struct FileReferenceAccumulator { push(data.vwebpage()); }, [&](const MTPDmessageMediaGame &data) { push(data.vgame()); + }, [&](const MTPDmessageMediaInvoice &data) { + push(data.vextended_media()); + }, [&](const MTPDmessageMediaPaidMedia &data) { + push(data.vextended_media()); }, [](const auto &data) { }); } From 043e3ae97ecf5e7444410dbb88145069c2c3c0e6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 21 Jun 2024 18:09:25 +0400 Subject: [PATCH 108/142] Use star emoji fallback for credits custom emoji. --- Telegram/SourceFiles/boxes/send_credits_box.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index ed48b04ff..442abf25a 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -358,7 +358,8 @@ TextWithEntities CreditsEmoji(not_null session) { session->data().customEmojiManager().registerInternalEmoji( st::settingsPremiumIconStar, QMargins{ 0, -st::moderateBoxExpandInnerSkip, 0, 0 }, - true)); + true), + QString(QChar(0x2B50))); } } // namespace Ui From 57254ca259eba46f16abb558065885435fb1b3dc Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 21 Jun 2024 18:09:49 +0400 Subject: [PATCH 109/142] Show a credits emoji in chats list preview. --- Telegram/SourceFiles/data/data_media_types.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 9ccb6ea4e..7d0025e8c 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_media_types.h" #include "base/random.h" +#include "boxes/send_credits_box.h" // CreditsEmoji. #include "history/history.h" #include "history/history_item.h" // CreateMedia. #include "history/history_location_manager.h" @@ -2066,8 +2067,11 @@ ItemPreview MediaInvoice::toPreview(ToPreviewOptions options) const { ? parent()->translatedText() : parent()->originalText(); const auto hasMiniImages = !images.empty(); + auto nice = Ui::Text::Colorized( + Ui::CreditsEmoji(&parent()->history()->session())); + nice.append(WithCaptionNotificationText(type, caption, hasMiniImages)); return { - .text = WithCaptionNotificationText(type, caption, hasMiniImages), + .text = std::move(nice), .images = std::move(images), .loadingContext = std::move(context), }; From 1399d2501d681374bb986b0a01db4f34c56e5b70 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 21 Jun 2024 18:10:04 +0400 Subject: [PATCH 110/142] Show first paid media in transactions history. --- Telegram/SourceFiles/api/api_credits.cpp | 35 ++++++++- Telegram/SourceFiles/boxes/send_files_box.cpp | 9 ++- Telegram/SourceFiles/data/data_credits.h | 10 +++ .../info_statistics_list_controllers.cpp | 20 +++-- .../settings/settings_credits_graphics.cpp | 66 +++++++++------- .../settings/settings_credits_graphics.h | 10 +++ .../ui/effects/credits_graphics.cpp | 77 ++++++++++++++++++- .../SourceFiles/ui/effects/credits_graphics.h | 13 ++++ 8 files changed, 200 insertions(+), 40 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 4473b78cd..e68ff026e 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/unixtime.h" #include "data/data_channel.h" +#include "data/data_document.h" #include "data/data_peer.h" #include "data/data_photo.h" #include "data/data_session.h" @@ -28,9 +29,40 @@ constexpr auto kTransactionsLimit = 100; const MTPStarsTransaction &tl, not_null peer) { using HistoryPeerTL = MTPDstarsTransactionPeer; + using namespace Data; + const auto owner = &peer->owner(); const auto photo = tl.data().vphoto() - ? peer->owner().photoFromWeb(*tl.data().vphoto(), ImageLocation()) + ? owner->photoFromWeb(*tl.data().vphoto(), ImageLocation()) : nullptr; + auto extended = std::vector(); + if (const auto list = tl.data().vextended_media()) { + extended.reserve(list->v.size()); + for (const auto &media : list->v) { + media.match([&](const MTPDmessageMediaPhoto &photo) { + if (const auto inner = photo.vphoto()) { + const auto photo = owner->processPhoto(*inner); + if (!photo->isNull()) { + extended.push_back(CreditsHistoryEntry::Media{ + .type = CreditsHistoryEntry::MediaType::Photo, + .mediaId = photo->id, + }); + } + } + }, [&](const MTPDmessageMediaDocument &document) { + if (const auto inner = document.vdocument()) { + const auto document = owner->processDocument(*inner); + if (document->isAnimation() + || document->isVideoFile() + || document->isGifv()) { + extended.push_back(CreditsHistoryEntry::Media{ + .type = CreditsHistoryEntry::MediaType::Video, + .mediaId = document->id, + }); + } + } + }, [&](const auto &) {}); + } + } const auto barePeerId = tl.data().vpeer().match([]( const HistoryPeerTL &p) { return peerFromMTP(p.vpeer()); @@ -53,6 +85,7 @@ constexpr auto kTransactionsLimit = 100; .description = qs(tl.data().vdescription().value_or_empty()), .date = base::unixtime::parse(tl.data().vdate().v), .photoId = photo ? photo->id : 0, + .extended = std::move(extended), .credits = tl.data().vstars().v, .bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()), .barePeerId = barePeerId, diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index d1f5cfeb6..6d67aa63d 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_media_prepare.h" #include "iv/iv_instance.h" #include "mainwidget.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "main/main_session_settings.h" #include "mtproto/mtproto_config.h" @@ -64,7 +65,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { constexpr auto kMaxMessageLength = 4096; -constexpr auto kMaxPrice = 1000ULL; using Ui::SendFilesWay; @@ -125,6 +125,9 @@ void EditPriceBox( 0, st::defaultSubsectionTitlePadding.right(), 0))); + const auto limit = session->appConfig().get( + u"stars_paid_post_amount_max"_q, + 10'000); const auto wrap = box->addRow(object_ptr( box, st::editTagField.heightMin)); @@ -133,7 +136,7 @@ void EditPriceBox( st::editTagField, tr::lng_paid_cost_placeholder(), price ? QString::number(price) : QString(), - kMaxPrice); + limit); const auto field = owned.data(); wrap->widthValue() | rpl::start_with_next([=](int width) { field->move(0, 0); @@ -167,7 +170,7 @@ void EditPriceBox( const auto save = [=] { const auto now = field->getLastText().toULongLong(); - if (now > kMaxPrice) { + if (now > limit) { field->showError(); return; } diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index 74fe1c4eb..e8d3aec66 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -30,11 +30,21 @@ struct CreditsHistoryEntry final { PremiumBot, Ads, }; + enum class MediaType { + Photo, + Video, + }; + struct Media { + MediaType type = MediaType::Photo; + uint64 mediaId = 0; + }; + QString id; QString title; QString description; QDateTime date; PhotoId photoId = 0; + std::vector extended; uint64 credits = 0; uint64 bareMsgId = 0; uint64 barePeerId = 0; diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index eb3292987..956c7d0c3 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -756,20 +756,24 @@ private: QString _name; Ui::Text::String _rightText; + + base::has_weak_ptr _guard; }; -CreditsRow::CreditsRow(not_null peer, const Descriptor &descriptor) +CreditsRow::CreditsRow( + not_null peer, + const Descriptor &descriptor) : PeerListRow(peer, UniqueRowIdFromEntry(descriptor.entry)) , _entry(descriptor.entry) , _creditIcon(descriptor.creditIcon) , _rowHeight(descriptor.rowHeight) { - const auto photo = _entry.photoId - ? peer->session().data().photo(_entry.photoId).get() - : nullptr; - if (photo) { - _paintUserpicCallback = Ui::GenerateCreditsPaintEntryCallback( - photo, - [this, update = descriptor.updateCallback] { update(this); }); + const auto callback = Ui::PaintPreviewCallback( + &peer->session(), + _entry); + if (callback) { + _paintUserpicCallback = callback(crl::guard(&_guard, [this, update = descriptor.updateCallback] { + update(this); + })); } init(); } diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index b5c890309..5b1ab8374 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/gift_premium_box.h" #include "core/click_handler_types.h" #include "core/ui_integration.h" +#include "data/data_document.h" #include "data/data_file_origin.h" #include "core/click_handler_types.h" // UrlClickHandler #include "data/data_photo_media.h" @@ -350,18 +351,16 @@ void ReceiptCreditsBox( using Type = Data::CreditsHistoryEntry::PeerType; const auto &stUser = st::boostReplaceUserpic; + const auto session = &controller->session(); const auto peer = (e.peerType == Type::PremiumBot) ? premiumBot : e.barePeerId - ? controller->session().data().peer(PeerId(e.barePeerId)).get() + ? session->data().peer(PeerId(e.barePeerId)).get() : nullptr; - const auto photo = e.photoId - ? controller->session().data().photo(e.photoId).get() - : nullptr; - if (photo) { + if (const auto callback = Ui::PaintPreviewCallback(session, e)) { content->add(object_ptr>( content, - HistoryEntryPhoto(content, photo, stUser.photoSize))); + GenericEntryPhoto(content, callback, stUser.photoSize))); } else if (peer) { content->add(object_ptr>( content, @@ -539,18 +538,16 @@ void ReceiptCreditsBox( }, button->lifetime()); } -object_ptr HistoryEntryPhoto( +object_ptr GenericEntryPhoto( not_null parent, - not_null photo, + Fn(Fn)> callback, int photoSize) { auto owned = object_ptr(parent); const auto widget = owned.data(); widget->resize(Size(photoSize)); - const auto draw = Ui::GenerateCreditsPaintEntryCallback( - photo, - [=] { widget->update(); }); - + const auto draw = callback( + crl::guard(widget, [=] { widget->update(); })); widget->paintRequest( ) | rpl::start_with_next([=] { auto p = Painter(widget); @@ -560,25 +557,40 @@ object_ptr HistoryEntryPhoto( return owned; } +object_ptr HistoryEntryPhoto( + not_null parent, + not_null photo, + int photoSize) { + return GenericEntryPhoto( + parent, + [=](Fn update) { + return Ui::GenerateCreditsPaintEntryCallback(photo, update); + }, + photoSize); +} + +object_ptr HistoryEntryVideo( + not_null parent, + not_null video, + int photoSize) { + return GenericEntryPhoto( + parent, + [=](Fn update) { + return Ui::GenerateCreditsPaintEntryCallback(video, update); + }, + photoSize); +} + object_ptr PaidMediaPhoto( not_null parent, not_null photo, int photoSize) { - auto owned = object_ptr(parent); - const auto widget = owned.data(); - widget->resize(Size(photoSize)); - - const auto draw = Ui::GeneratePaidMediaPaintCallback( - photo, - [=] { widget->update(); }); - - widget->paintRequest( - ) | rpl::start_with_next([=] { - auto p = Painter(widget); - draw(p, 0, 0, photoSize, photoSize); - }, widget->lifetime()); - - return owned; + return GenericEntryPhoto( + parent, + [=](Fn update) { + return Ui::GeneratePaidMediaPaintCallback(photo, update); + }, + photoSize); } void SmallBalanceBox( diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 8119213b7..2bc3f7f1c 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -55,11 +55,21 @@ void ReceiptCreditsBox( PeerData *premiumBot, const Data::CreditsHistoryEntry &e); +[[nodiscard]] object_ptr GenericEntryPhoto( + not_null parent, + Fn(Fn update)> callback, + int photoSize); + [[nodiscard]] object_ptr HistoryEntryPhoto( not_null parent, not_null photo, int photoSize); +[[nodiscard]] object_ptr HistoryEntryVideo( + not_null parent, + not_null video, + int photoSize); + [[nodiscard]] object_ptr PaidMediaPhoto( not_null parent, not_null photo, diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index 60af867b1..c662619b6 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_credits.h" #include "data/data_file_origin.h" +#include "data/data_document.h" +#include "data/data_document_media.h" #include "data/data_photo.h" #include "data/data_photo_media.h" #include "data/data_session.h" @@ -232,7 +234,53 @@ Fn GenerateCreditsPaintEntryCallback( minSize, minSize), size * style::DevicePixelRatio(), - { .options = Images::Option::RoundCircle }); + { .options = Images::Option::RoundLarge }); + } + p.drawImage(x, y, state->image); + }; +} + +Fn GenerateCreditsPaintEntryCallback( + not_null video, + Fn update) { + struct State { + std::shared_ptr view; + Image *imagePtr = nullptr; + QImage image; + rpl::lifetime downloadLifetime; + bool entryImageLoaded = false; + }; + const auto state = std::make_shared(); + state->view = video->createMediaView(); + video->loadThumbnail({}); + + rpl::single(rpl::empty_value()) | rpl::then( + video->owner().session().downloaderTaskFinished() + ) | rpl::start_with_next([=] { + using Size = Data::PhotoSize; + if (const auto thumbnail = state->view->thumbnail()) { + state->imagePtr = thumbnail; + } + update(); + if (state->imagePtr) { + state->entryImageLoaded = true; + state->downloadLifetime.destroy(); + } + }, state->downloadLifetime); + + return [=](Painter &p, int x, int y, int outerWidth, int size) { + if (state->imagePtr + && (!state->entryImageLoaded || state->image.isNull())) { + const auto image = state->imagePtr->original(); + const auto minSize = std::min(image.width(), image.height()); + state->image = Images::Prepare( + image.copy( + (image.width() - minSize) / 2, + (image.height() - minSize) / 2, + minSize, + minSize), + size * style::DevicePixelRatio(), + { .options = Images::Option::RoundLarge }); } p.drawImage(x, y, state->image); }; @@ -282,6 +330,33 @@ Fn GeneratePaidMediaPaintCallback( }; } +Fn(Fn)> PaintPreviewCallback( + not_null session, + const Data::CreditsHistoryEntry &entry) { + using MediaType = Data::CreditsHistoryEntry::MediaType; + const auto &extended = entry.extended; + const auto owner = &session->data(); + const auto photo = entry.photoId + ? owner->photo(entry.photoId).get() + : (!extended.empty() && extended.front().type == MediaType::Photo) + ? owner->photo(extended.front().mediaId).get() + : nullptr; + const auto video = (!extended.empty() + && extended.front().type == MediaType::Video) + ? owner->document(extended.front().mediaId).get() + : nullptr; + if (photo) { + return [=](Fn update) { + return Ui::GenerateCreditsPaintEntryCallback(photo, update); + }; + } else if (video) { + return [=](Fn update) { + return Ui::GenerateCreditsPaintEntryCallback(video, update); + }; + } + return nullptr; +} + TextWithEntities GenerateEntryName(const Data::CreditsHistoryEntry &entry) { return ((entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment) ? tr::lng_bot_username_description1_link diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.h b/Telegram/SourceFiles/ui/effects/credits_graphics.h index 343910adf..4585fe7c0 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.h +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.h @@ -8,11 +8,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once class PhotoData; +class DocumentData; namespace Data { struct CreditsHistoryEntry; } // namespace Data +namespace Main { +class Session; +} // namespace Main + namespace Ui { class MaskedInputField; class RpWidget; @@ -38,10 +43,18 @@ Fn GenerateCreditsPaintEntryCallback( not_null photo, Fn update); +Fn GenerateCreditsPaintEntryCallback( + not_null video, + Fn update); + Fn GeneratePaidMediaPaintCallback( not_null photo, Fn update); +Fn(Fn)> PaintPreviewCallback( + not_null session, + const Data::CreditsHistoryEntry &entry); + [[nodiscard]] TextWithEntities GenerateEntryName( const Data::CreditsHistoryEntry &entry); From d81e832ae61cc143ab0dbc621510e88ff6e709d8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 23 Jun 2024 09:47:53 +0400 Subject: [PATCH 111/142] Use lang key for about stars link. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/boxes/send_files_box.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index fb8aa23e7..80441cd03 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3345,6 +3345,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_paid_cost_placeholder" = "Stars to Unlock"; "lng_paid_about" = "Users will have to transfer this amount of Stars to your channel in order to view this media. {link}"; "lng_paid_about_link" = "More about stars >"; +"lng_paid_about_link_url" = "https://telegram.org/blog/telegram-stars"; "lng_paid_price" = "Unlock for {price}"; "lng_translate_show_original" = "Show Original"; diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 6d67aa63d..ea74b4d00 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -159,7 +159,7 @@ void EditPriceBox( about->setClickHandlerFilter([=](const auto &...) { Core::App().iv().openWithIvPreferred( session, - u"https://telegram.org/blog/telegram-stars"_q); + tr::lng_paid_about_link_url(tr::now)); return false; }); From 54cc12cf224bccd01f8ff424ffda3449e7a8e48a Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 24 Jun 2024 12:23:31 +0400 Subject: [PATCH 112/142] Show media payments as outgoing. --- Telegram/SourceFiles/api/api_credits.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index e68ff026e..18c9ee5d9 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -113,7 +113,8 @@ constexpr auto kTransactionsLimit = 100; .successLink = qs(tl.data().vtransaction_url().value_or_empty()), .in = (!isBot || tl.data().is_refund()) && !tl.data().is_pending() - && !tl.data().is_failed(), + && !tl.data().is_failed() + && !tl.data().vextended_media(), }; } From dfc422b50500b533c873001873861958b25c9c30 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 24 Jun 2024 12:23:42 +0400 Subject: [PATCH 113/142] Show paid media albums with context. --- .../media/view/media_view_group_thumbs.cpp | 22 +++++++++++++++++++ .../media/view/media_view_group_thumbs.h | 8 +++++++ .../media/view/media_view_overlay_widget.cpp | 20 ++++++++++++++++- 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp b/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp index 2aff46067..c03c20c8e 100644 --- a/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp +++ b/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp @@ -669,6 +669,11 @@ auto GroupThumbs::createThumb(Key key) key, page->collage, collageKey->index); + } else if (const auto invoice = media->invoice()) { + return createThumb( + key, + *invoice, + collageKey->index); } } } @@ -695,6 +700,23 @@ auto GroupThumbs::createThumb( return createThumb(key, nullptr); } +auto GroupThumbs::createThumb( + Key key, + const Data::Invoice &invoice, + int index) +-> std::unique_ptr { + if (index < 0 || index >= invoice.extendedMedia.size()) { + return createThumb(key, nullptr); + } + const auto &media = invoice.extendedMedia[index]; + if (const auto photo = media->photo()) { + return createThumb(key, photo); + } else if (const auto document = media->document()) { + return createThumb(key, document); + } + return createThumb(key, nullptr); +} + auto GroupThumbs::createThumb(Key key, std::nullptr_t) -> std::unique_ptr { const auto weak = base::make_weak(this); diff --git a/Telegram/SourceFiles/media/view/media_view_group_thumbs.h b/Telegram/SourceFiles/media/view/media_view_group_thumbs.h index b624c985d..ebcdc3737 100644 --- a/Telegram/SourceFiles/media/view/media_view_group_thumbs.h +++ b/Telegram/SourceFiles/media/view/media_view_group_thumbs.h @@ -15,6 +15,10 @@ class SharedMediaWithLastSlice; class UserPhotosSlice; struct WebPageCollage; +namespace Data { +struct Invoice; +} // namespace Data + namespace Main { class Session; } // namespace Main @@ -109,6 +113,10 @@ private: Key key, const WebPageCollage &collage, int index); + std::unique_ptr createThumb( + Key key, + const Data::Invoice &invoice, + int index); std::unique_ptr createThumb(Key key, not_null photo); std::unique_ptr createThumb( Key key, diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 33f08c5a1..0dde74e30 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -2771,7 +2771,7 @@ auto OverlayWidget::sharedMediaType() const using Type = SharedMediaType; if (_message) { if (const auto media = _message->media()) { - if (media->webpage()) { + if (media->webpage() || media->invoice()) { return std::nullopt; } } @@ -2999,6 +2999,14 @@ std::optional OverlayWidget::collageKey() const { return item; } } + } else if (const auto invoice = media->invoice()) { + for (const auto &item : invoice->extendedMedia) { + if (_photo && item->photo() == _photo) { + return _photo; + } else if (_document && item->document() == _document) { + return _document; + } + } } } } @@ -3032,6 +3040,16 @@ void OverlayWidget::validateCollage() { if (const auto media = _message->media()) { if (const auto page = media->webpage()) { _collageData = page->collage; + } else if (const auto invoice = media->invoice()) { + auto &data = *_collageData; + data.items.reserve(invoice->extendedMedia.size()); + for (const auto &item : invoice->extendedMedia) { + if (const auto photo = item->photo()) { + data.items.push_back(photo); + } else if (const auto document = item->document()) { + data.items.push_back(document); + } + } } } } From 90dfae52f59f7ea33d27bb1fcae063fa60f0db69 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 24 Jun 2024 14:48:47 +0400 Subject: [PATCH 114/142] Show nice thumbnails for paid albums. --- Telegram/SourceFiles/api/api_credits.cpp | 14 +- .../SourceFiles/boxes/send_credits_box.cpp | 17 +- Telegram/SourceFiles/data/data_credits.h | 20 ++- .../settings/settings_credits_graphics.cpp | 22 +-- .../settings/settings_credits_graphics.h | 9 +- .../ui/effects/credits_graphics.cpp | 166 +++++++++++++----- .../SourceFiles/ui/effects/credits_graphics.h | 27 ++- 7 files changed, 187 insertions(+), 88 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 18c9ee5d9..30f47db63 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -34,7 +34,7 @@ constexpr auto kTransactionsLimit = 100; const auto photo = tl.data().vphoto() ? owner->photoFromWeb(*tl.data().vphoto(), ImageLocation()) : nullptr; - auto extended = std::vector(); + auto extended = std::vector(); if (const auto list = tl.data().vextended_media()) { extended.reserve(list->v.size()); for (const auto &media : list->v) { @@ -42,9 +42,9 @@ constexpr auto kTransactionsLimit = 100; if (const auto inner = photo.vphoto()) { const auto photo = owner->processPhoto(*inner); if (!photo->isNull()) { - extended.push_back(CreditsHistoryEntry::Media{ - .type = CreditsHistoryEntry::MediaType::Photo, - .mediaId = photo->id, + extended.push_back(CreditsHistoryMedia{ + .type = CreditsHistoryMediaType::Photo, + .id = photo->id, }); } } @@ -54,9 +54,9 @@ constexpr auto kTransactionsLimit = 100; if (document->isAnimation() || document->isVideoFile() || document->isGifv()) { - extended.push_back(CreditsHistoryEntry::Media{ - .type = CreditsHistoryEntry::MediaType::Video, - .mediaId = document->id, + extended.push_back(CreditsHistoryMedia{ + .type = CreditsHistoryMediaType::Video, + .id = document->id, }); } } diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index 442abf25a..cd2cefacd 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -156,11 +156,18 @@ struct PaidMediaData { not_null form, int photoSize) { if (const auto data = LookupPaidMediaData(session, form)) { - const auto first = data.invoice->extendedMedia.front().get(); - if (const auto photo = first->photo()) { - if (photo->extendedMediaPreview()) { - return Settings::PaidMediaPhoto(parent, photo, photoSize); - } + const auto first = data.invoice->extendedMedia[0]->photo(); + const auto second = (data.photos > 1) + ? data.invoice->extendedMedia[1]->photo() + : nullptr; + const auto totalCount = int(data.invoice->extendedMedia.size()); + if (first && first->extendedMediaPreview()) { + return Settings::PaidMediaThumbnail( + parent, + first, + second, + totalCount, + photoSize); } } if (form->photo) { diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index e8d3aec66..75da4db5b 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -19,6 +19,16 @@ struct CreditTopupOption final { using CreditTopupOptions = std::vector; +enum class CreditsHistoryMediaType { + Photo, + Video, +}; + +struct CreditsHistoryMedia { + CreditsHistoryMediaType type = CreditsHistoryMediaType::Photo; + uint64 id = 0; +}; + struct CreditsHistoryEntry final { using PhotoId = uint64; enum class PeerType { @@ -30,21 +40,13 @@ struct CreditsHistoryEntry final { PremiumBot, Ads, }; - enum class MediaType { - Photo, - Video, - }; - struct Media { - MediaType type = MediaType::Photo; - uint64 mediaId = 0; - }; QString id; QString title; QString description; QDateTime date; PhotoId photoId = 0; - std::vector extended; + std::vector extended; uint64 credits = 0; uint64 bareMsgId = 0; uint64 barePeerId = 0; diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 5b1ab8374..4004c04bd 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -569,26 +569,20 @@ object_ptr HistoryEntryPhoto( photoSize); } -object_ptr HistoryEntryVideo( - not_null parent, - not_null video, - int photoSize) { - return GenericEntryPhoto( - parent, - [=](Fn update) { - return Ui::GenerateCreditsPaintEntryCallback(video, update); - }, - photoSize); -} - -object_ptr PaidMediaPhoto( +object_ptr PaidMediaThumbnail( not_null parent, not_null photo, + PhotoData *second, + int totalCount, int photoSize) { return GenericEntryPhoto( parent, [=](Fn update) { - return Ui::GeneratePaidMediaPaintCallback(photo, update); + return Ui::GeneratePaidMediaPaintCallback( + photo, + second, + totalCount, + update); }, photoSize); } diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 2bc3f7f1c..efea501b9 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -65,14 +65,11 @@ void ReceiptCreditsBox( not_null photo, int photoSize); -[[nodiscard]] object_ptr HistoryEntryVideo( - not_null parent, - not_null video, - int photoSize); - -[[nodiscard]] object_ptr PaidMediaPhoto( +[[nodiscard]] object_ptr PaidMediaThumbnail( not_null parent, not_null photo, + PhotoData *second, + int totalCount, int photoSize); void SmallBalanceBox( diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index c662619b6..6cd783f89 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -37,21 +37,62 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include namespace Ui { +namespace { -using PaintRoundImageCallback = Fn; +PaintRoundImageCallback MultiThumbnail( + PaintRoundImageCallback first, + PaintRoundImageCallback second, + int totalCount) { + const auto cache = std::make_shared(); + return [=](Painter &p, int x, int y, int outerWidth, int size) { + const auto stroke = st::lineWidth * 2; + const auto shift = stroke * 3; + if (size <= 2 * shift) { + first(p, x, y, outerWidth, size); + return; + } + const auto smaller = size - shift; + const auto ratio = style::DevicePixelRatio(); + const auto full = QSize(size, size) * ratio; + if (cache->size() != full) { + *cache = QImage(full, QImage::Format_ARGB32_Premultiplied); + cache->setDevicePixelRatio(ratio); + } + cache->fill(Qt::transparent); + auto q = Painter(cache.get()); + second(q, shift, 0, outerWidth, smaller); + q.setCompositionMode(QPainter::CompositionMode_Source); + q.setPen(QPen(Qt::transparent, 2 * stroke)); + q.setBrush(Qt::NoBrush); + const auto radius = st::roundRadiusLarge; + auto hq = PainterHighQualityEnabler(q); + q.drawRoundedRect(QRect(0, shift, smaller, smaller), radius, radius); + q.setCompositionMode(QPainter::CompositionMode_SourceOver); + first(q, 0, shift, outerWidth, smaller); + q.setPen(Qt::NoPen); + q.setBrush(st::shadowFg); + q.drawRoundedRect(QRect(0, shift, smaller, smaller), radius, radius); + q.setPen(st::toastFg); + q.setFont(style::font(smaller / 2, style::FontFlag::Semibold, 0)); + q.drawText( + QRect(0, shift, smaller, smaller), + QString::number(totalCount), + style::al_center); + q.end(); + + p.drawImage(x, y, *cache); + }; +} + +} // namespace QImage GenerateStars(int height, int count) { constexpr auto kOutlineWidth = .6; constexpr auto kStrokeWidth = 3; constexpr auto kShift = 3; - auto colorized = qs(Ui::Premium::ColorizedSvg( - Ui::Premium::CreditsIconGradientStops())); + auto colorized = qs(Premium::ColorizedSvg( + Premium::CreditsIconGradientStops())); colorized.replace( u"stroke=\"none\""_q, u"stroke=\"%1\""_q.arg(st::creditsStroke->c.name())); @@ -98,10 +139,10 @@ QImage GenerateStars(int height, int count) { return frame; } -not_null CreateSingleStarWidget( - not_null parent, +not_null CreateSingleStarWidget( + not_null parent, int height) { - const auto widget = Ui::CreateChild(parent); + const auto widget = CreateChild(parent); const auto image = GenerateStars(height, 1); widget->resize(image.size() / style::DevicePixelRatio()); widget->paintRequest( @@ -113,15 +154,15 @@ not_null CreateSingleStarWidget( return widget; } -not_null AddInputFieldForCredits( - not_null container, +not_null AddInputFieldForCredits( + not_null container, rpl::producer value) { const auto &st = st::botEarnInputField; const auto inputContainer = container->add( CreateSkipWidget(container, st.heightMin)); const auto currentValue = rpl::variable( rpl::duplicate(value)); - const auto input = Ui::CreateChild( + const auto input = CreateChild( inputContainer, st, tr::lng_bot_earn_out_ph(), @@ -146,16 +187,16 @@ not_null AddInputFieldForCredits( st::boxRowPadding.left(), st.textMargins.top()); }, input->lifetime()); - Ui::ToggleChildrenVisibility(inputContainer, true); + ToggleChildrenVisibility(inputContainer, true); return input; } PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( const Data::CreditsHistoryEntry &entry) { - const auto bg = [&]() -> Ui::EmptyUserpic::BgColors { + const auto bg = [&]() -> EmptyUserpic::BgColors { switch (entry.peerType) { case Data::CreditsHistoryEntry::PeerType::Peer: - return Ui::EmptyUserpic::UserpicColor(0); + return EmptyUserpic::UserpicColor(0); case Data::CreditsHistoryEntry::PeerType::AppStore: return { st::historyPeer7UserpicBg, st::historyPeer7UserpicBg2 }; case Data::CreditsHistoryEntry::PeerType::PlayMarket: @@ -172,7 +213,7 @@ PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( } Unexpected("Unknown peer type."); }(); - const auto userpic = std::make_shared(bg, QString()); + const auto userpic = std::make_shared(bg, QString()); return [=](Painter &p, int x, int y, int outerWidth, int size) mutable { userpic->paintCircle(p, x, y, outerWidth, size); using PeerType = Data::CreditsHistoryEntry::PeerType; @@ -190,7 +231,7 @@ PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( }; } -Fn GenerateCreditsPaintEntryCallback( +PaintRoundImageCallback GenerateCreditsPaintEntryCallback( not_null photo, Fn update) { struct State { @@ -240,7 +281,7 @@ Fn GenerateCreditsPaintEntryCallback( }; } -Fn GenerateCreditsPaintEntryCallback( +PaintRoundImageCallback GenerateCreditsPaintEntryCallback( not_null video, Fn update) { struct State { @@ -286,7 +327,36 @@ Fn GenerateCreditsPaintEntryCallback( }; } -Fn GeneratePaidMediaPaintCallback( +PaintRoundImageCallback GenerateCreditsPaintEntryCallback( + not_null session, + Data::CreditsHistoryMedia media, + Fn update) { + return (media.type == Data::CreditsHistoryMediaType::Photo) + ? GenerateCreditsPaintEntryCallback( + session->data().photo(media.id), + std::move(update)) + : GenerateCreditsPaintEntryCallback( + session->data().document(media.id), + std::move(update)); +} + +PaintRoundImageCallback GenerateCreditsPaintEntryCallback( + not_null session, + const std::vector &media, + Fn update) { + if (media.size() == 1) { + return GenerateCreditsPaintEntryCallback( + session, + media.front(), + std::move(update)); + } + return MultiThumbnail( + GenerateCreditsPaintEntryCallback(session, media[0], update), + GenerateCreditsPaintEntryCallback(session, media[1], update), + media.size()); +} + +PaintRoundImageCallback GeneratePaidPhotoPaintCallback( not_null photo, Fn update) { struct State { @@ -294,7 +364,8 @@ Fn GeneratePaidMediaPaintCallback( } QImage image; - Ui::SpoilerAnimation spoiler; + QImage spoilerCornerCache; + SpoilerAnimation spoiler; }; const auto state = std::make_shared(update); @@ -322,36 +393,49 @@ Fn GeneratePaidMediaPaintCallback( { .options = Images::Option::RoundLarge }); } p.drawImage(x, y, state->image); - Ui::FillSpoilerRect( + FillSpoilerRect( p, QRect(x, y, size, size), - Ui::DefaultImageSpoiler().frame( - state->spoiler.index(crl::now(), false))); + Images::CornersMaskRef( + Images::CornersMask(ImageRoundRadius::Large)), + DefaultImageSpoiler().frame( + state->spoiler.index(crl::now(), false)), + state->spoilerCornerCache); }; } -Fn(Fn)> PaintPreviewCallback( +PaintRoundImageCallback GeneratePaidMediaPaintCallback( + not_null photo, + PhotoData *second, + int totalCount, + Fn update) { + if (!second) { + return GeneratePaidPhotoPaintCallback(photo, update); + } + return MultiThumbnail( + GeneratePaidPhotoPaintCallback(photo, update), + GeneratePaidPhotoPaintCallback(second, update), + totalCount); +} + +Fn)> PaintPreviewCallback( not_null session, const Data::CreditsHistoryEntry &entry) { - using MediaType = Data::CreditsHistoryEntry::MediaType; + using MediaType = Data::CreditsHistoryMediaType; const auto &extended = entry.extended; - const auto owner = &session->data(); - const auto photo = entry.photoId - ? owner->photo(entry.photoId).get() - : (!extended.empty() && extended.front().type == MediaType::Photo) - ? owner->photo(extended.front().mediaId).get() - : nullptr; - const auto video = (!extended.empty() - && extended.front().type == MediaType::Video) - ? owner->document(extended.front().mediaId).get() - : nullptr; - if (photo) { + if (!extended.empty()) { return [=](Fn update) { - return Ui::GenerateCreditsPaintEntryCallback(photo, update); + return GenerateCreditsPaintEntryCallback( + session, + extended, + std::move(update)); }; - } else if (video) { + } else if (entry.photoId) { + const auto photo = session->data().photo(entry.photoId); return [=](Fn update) { - return Ui::GenerateCreditsPaintEntryCallback(video, update); + return GenerateCreditsPaintEntryCallback( + photo, + std::move(update)); }; } return nullptr; diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.h b/Telegram/SourceFiles/ui/effects/credits_graphics.h index 4585fe7c0..70d983056 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.h +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.h @@ -12,6 +12,7 @@ class DocumentData; namespace Data { struct CreditsHistoryEntry; +struct CreditsHistoryMedia; } // namespace Data namespace Main { @@ -26,6 +27,13 @@ class VerticalLayout; namespace Ui { +using PaintRoundImageCallback = Fn; + [[nodiscard]] QImage GenerateStars(int height, int count); [[nodiscard]] not_null CreateSingleStarWidget( @@ -36,22 +44,29 @@ namespace Ui { not_null container, rpl::producer value); -Fn GenerateCreditsPaintUserpicCallback( +PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( const Data::CreditsHistoryEntry &entry); -Fn GenerateCreditsPaintEntryCallback( +PaintRoundImageCallback GenerateCreditsPaintEntryCallback( not_null photo, Fn update); -Fn GenerateCreditsPaintEntryCallback( +PaintRoundImageCallback GenerateCreditsPaintEntryCallback( not_null video, Fn update); -Fn GeneratePaidMediaPaintCallback( - not_null photo, +PaintRoundImageCallback GenerateCreditsPaintEntryCallback( + not_null session, + Data::CreditsHistoryMedia media, Fn update); -Fn(Fn)> PaintPreviewCallback( +PaintRoundImageCallback GeneratePaidMediaPaintCallback( + not_null photo, + PhotoData *second, + int totalCount, + Fn update); + +Fn)> PaintPreviewCallback( not_null session, const Data::CreditsHistoryEntry &entry); From a39a8dbd2c8b793543b65e6da6934f216c9d1dbe Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 24 Jun 2024 17:21:17 +0400 Subject: [PATCH 115/142] Show paid media from transactions history. --- Telegram/SourceFiles/history/history_item.cpp | 6 ++ Telegram/SourceFiles/history/history_item.h | 1 + .../settings/settings_credits_graphics.cpp | 100 +++++++++++++++++- 3 files changed, 106 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index da40ecec5..7203aceb1 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -5554,3 +5554,9 @@ void HistoryItem::clearDependencyMessage() { } } } + +void HistoryItem::overrideMedia(std::unique_ptr media) { + Expects(!media || media->parent() == this); + + _media = std::move(media); +} diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index bb23aa3fd..e4ed45cf9 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -356,6 +356,7 @@ public: const MTPDupdateShortSentMessage &data, bool wasAlready); void updateReactions(const MTPMessageReactions *reactions); + void overrideMedia(std::unique_ptr media); void applyEditionToHistoryCleared(); void updateReplyMarkup(HistoryMessageMarkupData &&markup); diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 4004c04bd..3e720ba59 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -21,6 +21,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_user.h" #include "data/stickers/data_custom_emoji.h" +#include "history/history.h" +#include "history/history_item.h" #include "info/settings/info_settings_widget.h" // SectionCustomTopBarData. #include "info/statistics/info_statistics_list_controllers.h" #include "lang/lang_keys.h" @@ -124,6 +126,101 @@ private: }; +void AddViewMediaHandler( + not_null thumb, + not_null controller, + const Data::CreditsHistoryEntry &e) { + if (e.extended.empty()) { + return; + } + thumb->setCursor(style::cur_pointer); + + struct State { + ~State() { + if (item) { + item->destroy(); + } + } + + HistoryItem *item = nullptr; + bool pressed = false; + bool over = false; + }; + const auto state = thumb->lifetime().make_state(); + const auto session = &controller->session(); + const auto owner = &session->data(); + const auto peerId = e.barePeerId + ? PeerId(e.barePeerId) + : session->userPeerId(); + const auto history = owner->history(session->user()); + state->item = history->makeMessage({ + .id = history->nextNonHistoryEntryId(), + .flags = MessageFlag::HasFromId | MessageFlag::AdminLogEntry, + .from = peerId, + .date = base::unixtime::serialize(e.date), + }, TextWithEntities(), MTP_messageMediaEmpty()); + auto fake = std::vector>(); + fake.reserve(e.extended.size()); + for (const auto &item : e.extended) { + if (item.type == Data::CreditsHistoryMediaType::Photo) { + fake.push_back(std::make_unique( + state->item, + owner->photo(item.id), + false)); // spoiler + } else { + fake.push_back(std::make_unique( + state->item, + owner->document(item.id), + true, // skipPremiumEffect + false, // spoiler + 0)); // ttlSeconds + } + } + state->item->overrideMedia(std::make_unique( + state->item, + Data::Invoice{ + .amount = uint64(std::abs(int64(e.credits))), + .currency = Ui::kCreditsCurrency, + .extendedMedia = std::move(fake), + .isPaidMedia = true, + })); + const auto showMedia = crl::guard(controller, [=] { + if (const auto media = state->item->media()) { + if (const auto invoice = media->invoice()) { + if (!invoice->extendedMedia.empty()) { + const auto first = invoice->extendedMedia[0].get(); + if (const auto photo = first->photo()) { + controller->openPhoto(photo, { + .id = state->item->fullId(), + }); + } else if (const auto document = first->document()) { + controller->openDocument(document, true, { + .id = state->item->fullId(), + }); + } + } + } + } + }); + thumb->events() | rpl::start_with_next([=](not_null e) { + if (e->type() == QEvent::MouseButtonPress) { + const auto mouse = static_cast(e.get()); + if (mouse->button() == Qt::LeftButton) { + state->over = true; + state->pressed = true; + } + } else if (e->type() == QEvent::MouseButtonRelease + && state->over + && state->pressed) { + showMedia(); + } else if (e->type() == QEvent::Enter) { + state->over = true; + } else if (e->type() == QEvent::Leave) { + state->over = false; + } + }, thumb->lifetime()); +} + } // namespace void FillCreditOptions( @@ -358,9 +455,10 @@ void ReceiptCreditsBox( ? session->data().peer(PeerId(e.barePeerId)).get() : nullptr; if (const auto callback = Ui::PaintPreviewCallback(session, e)) { - content->add(object_ptr>( + const auto thumb = content->add(object_ptr>( content, GenericEntryPhoto(content, callback, stUser.photoSize))); + AddViewMediaHandler(thumb->entity(), controller, e); } else if (peer) { content->add(object_ptr>( content, From 7e704d95294dc85aa0ea40dc4e4a3a0ac6b8e9eb Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 24 Jun 2024 22:21:52 +0400 Subject: [PATCH 116/142] Show price in my/purchased paid media. --- .../Resources/icons/payments/small_star.png | Bin 0 -> 484 bytes .../icons/payments/small_star@2x.png | Bin 0 -> 860 bytes .../icons/payments/small_star@3x.png | Bin 0 -> 1131 bytes .../SourceFiles/boxes/send_credits_box.cpp | 11 ++- Telegram/SourceFiles/boxes/send_credits_box.h | 3 + .../SourceFiles/data/data_media_types.cpp | 2 +- .../history/view/history_view_element.cpp | 8 ++ .../history/view/history_view_element.h | 6 ++ .../history/view/media/history_view_gif.cpp | 6 ++ .../history/view/media/history_view_gif.h | 1 + .../history/view/media/history_view_media.cpp | 71 ++++++++++++++++++ .../history/view/media/history_view_media.h | 6 ++ .../view/media/history_view_media_grouped.cpp | 6 +- .../view/media/history_view_media_grouped.h | 3 +- .../history/view/media/history_view_photo.cpp | 9 +++ .../history/view/media/history_view_photo.h | 3 +- Telegram/SourceFiles/ui/effects/credits.style | 3 + 17 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 Telegram/Resources/icons/payments/small_star.png create mode 100644 Telegram/Resources/icons/payments/small_star@2x.png create mode 100644 Telegram/Resources/icons/payments/small_star@3x.png diff --git a/Telegram/Resources/icons/payments/small_star.png b/Telegram/Resources/icons/payments/small_star.png new file mode 100644 index 0000000000000000000000000000000000000000..94e3fc4c5adfaccd1d5d3d216f23309c5c890b0e GIT binary patch literal 484 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgf%+J%sF~mYJ zIYEN8S%YO;?(J=9XQjMkMFa#s$n(sZGv|bPcW39zPft$^H1o;Z`RsGNc=4h@u)>n* z)5YaBOH6z!At~7y7Z4g68W0c=67uEq=f@8oG-&_$`T2Ppue6+9&5sus7gv9O_x9G- z*YEG|FNhRzD=ISDB>Z9X1FTk>@0g5Rase?n3%ZZ)aB*=yLau{SNmH`&#B?yv18Nb&HMM@ zAaja2)8hW=)2BE1rDXd1`zI_=l4eX~4i?*FWHFodoKspxc6R=~Ju_!Y9(^ppdg;=o z6Iu^*)GWAX3d+g#T@_g%m9WVnj59si+#@(xc+sP4YoiY&oH~EL{@WW#7nbRc1;T+6 zSFc`O?l+fdSFVdQ&MBb@ E09IGDo&W#< literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/payments/small_star@2x.png b/Telegram/Resources/icons/payments/small_star@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4dc7016fe9f52a81d6991d1095fd16b3a1f2525a GIT binary patch literal 860 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1SD@H6cd;@Y{&BD%a?a|b{2nr<{KJ%weh=-vGM1xudn}aK6tR1o&BEU$B!Qc zt~+?7r$28j5!Tjzy|?=N1lf`=FD^Eg1f->@ZOT^2DKG!tSSZZSetfdJzkzJYqa&S- zg#m$qg3GcMlMAQM+uPd<3tzszKK}pT-|gOpvn-UQ(ASMl+Ypv0YBrP;@NBo7>MUlJ#h+RiV3OjL79NBoWg#-mape8O7d zt;-iLZa%bJ$lbw#q2Kezq6a|ZL-a%b{rNe0(xjrt$9Qe>4ylXE$o%>FIo(T%Bl7+E z`S#4r%pY&M_sAFo9$xLAa`|CSg4(WFGPefKfuQVh+=ks{j9NH|Q+g_w|GtlXL2)CnxXk zuRs1?q~pz-o0}(o6-wDG5Yo8pz~#%uFE1@^YHG@Ov}4DeJ9l<0S)s6Q=~CB}0|%Jj z|M>VgWP`+X!PQ}Fn{K_2w1Ua5j`}_L(dTlMO zii!#aH*at6^K&dee}8}f#7EYY^z`)LEc39xhK7a(vm4%C|MaP7NAQHJ%*;hcyF?GD zDa8~P6&Y<}TD7s!HzGloag7U5JLfdLwzf8Dvm6GthrhnQ_Vl_EQv7a(BO6gA`6MbbAj}e$~^(F(iZa z?aWvWlR$~%(^ZQUmmXQyKi$d8({9p{$0C1v25G5D=Tcn zyLWm1Awhfg?735uGjZa??c2BKZ0POf{oMHV>(`Adms~q_%FB48tGoOC`}aRLv14WBl@$~`IP;Hh=D~vpQ&Ust9IZTP z@W(>CF8kKan={XSHT-z{=1oCKk4LrXX=zN%mG4{l|5OO+>FamDz8scVR3wxgCjaEu zl&(NK3yT%LGd(pdm zz+Aq3`RLK3z(D%;?c1zbv(~K9;d!=pt!}E3auegCT%(n#&lDFX+^zBSpi^r0v0>RCQBbMw_=U+N9)?d3OHZ>#cO+uPgwk3B$3Lq})M{Q2KiZe`cx z=le%qK0oosjTKN5zR!#>YDawAtUw?5ULCFU;XgO199`^IJ7( z_UzpoHXJxHeb$F=#n-FsFHD;?>y@YKgtoRe1KGe+2mH5YXJ^l|QtDeeVbk?~w@a5V zPyMw)NLoVTK$&^8S7_+fZ{N0UvR2wvpMTRUBx>WH*!cMAM;TY{-#@=z`CismGZT{p z=je3}!Zo5>*Uk$4(%G`XlV`@xojd2vliRb#bjqP`!eR+CuHV0V*Vo7A!|z28qpq~d kD6pSkENVcenk_zvCm631%Vhm?5tPwAUHx3vIVCg!0FeX$m;e9( literal 0 HcmV?d00001 diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index cd2cefacd..39d7983d7 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -285,7 +285,7 @@ void SendCreditsBox( lt_count, rpl::single(form->invoice.amount) | tr::to_count(), lt_emoji, - rpl::single(CreditsEmoji(session)), + rpl::single(CreditsEmojiSmall(session)), Ui::Text::RichLangValue); const auto buttonLabel = Ui::CreateChild( button, @@ -369,4 +369,13 @@ TextWithEntities CreditsEmoji(not_null session) { QString(QChar(0x2B50))); } +TextWithEntities CreditsEmojiSmall(not_null session) { + return Ui::Text::SingleCustomEmoji( + session->data().customEmojiManager().registerInternalEmoji( + st::starIconSmall, + st::starIconSmallPadding, + true), + QString(QChar(0x2B50))); +} + } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/send_credits_box.h b/Telegram/SourceFiles/boxes/send_credits_box.h index 71675c720..c0107a360 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.h +++ b/Telegram/SourceFiles/boxes/send_credits_box.h @@ -29,4 +29,7 @@ void SendCreditsBox( [[nodiscard]] TextWithEntities CreditsEmoji( not_null session); +[[nodiscard]] TextWithEntities CreditsEmojiSmall( + not_null session); + } // namespace Ui diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 7d0025e8c..72ca05542 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -2068,7 +2068,7 @@ ItemPreview MediaInvoice::toPreview(ToPreviewOptions options) const { : parent()->originalText(); const auto hasMiniImages = !images.empty(); auto nice = Ui::Text::Colorized( - Ui::CreditsEmoji(&parent()->history()->session())); + Ui::CreditsEmojiSmall(&parent()->history()->session())); nice.append(WithCaptionNotificationText(type, caption, hasMiniImages)); return { .text = std::move(nice), diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index b85001331..50f5b7454 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -721,6 +721,14 @@ void Element::overrideMedia(std::unique_ptr media) { _flags |= Flag::MediaOverriden; } +not_null Element::enforcePurchasedTag() { + if (const auto purchased = Get()) { + return purchased; + } + AddComponents(PurchasedTag::Bit()); + return Get(); +} + void Element::refreshMedia(Element *replacing) { if (_flags & Flag::MediaOverriden) { return; diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index d4139e8ec..1d52d7925 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -278,6 +278,10 @@ struct FakeBotAboutTop : public RuntimeComponent { int height = 0; }; +struct PurchasedTag : public RuntimeComponent { + Ui::Text::String text; +}; + struct TopicButton { std::unique_ptr ripple; ClickHandlerPtr link; @@ -564,6 +568,8 @@ public: void overrideMedia(std::unique_ptr media); + [[nodiscard]] not_null enforcePurchasedTag(); + virtual bool consumeHorizontalScroll(QPoint position, int delta) { return false; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 20748d9fa..ffe0d7757 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -191,6 +191,8 @@ Gif::Gif( } } ensureTranscribeButton(); + + _purchasedPriceTag = hasPurchasedTag(); } Gif::~Gif() { @@ -663,6 +665,10 @@ void Gif::draw(Painter &p, const PaintContext &context) const { const auto skipDrawingSurrounding = context.skipDrawingParts == PaintContext::SkipDrawingParts::Surrounding; + if (!skipDrawingSurrounding && _purchasedPriceTag) { + drawPurchasedTag(p, rthumb, context); + } + if (!unwrapped && !skipDrawingSurrounding) { if (!isRound || !inWebPage) { drawCornerStatus(p, context, QPoint()); diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index f0460bf0d..f86421f92 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -213,6 +213,7 @@ private: mutable bool _thumbCacheBlurred : 1 = false; mutable bool _thumbIsEllipse : 1 = false; mutable bool _pollingStory : 1 = false; + mutable bool _purchasedPriceTag : 1 = false; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index 4f8c8096b..28e4e0870 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/media/history_view_media.h" +#include "boxes/send_credits_box.h" // CreditsEmoji. #include "history/history.h" #include "history/history_item.h" #include "history/view/history_view_element.h" @@ -18,12 +19,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_session.h" #include "data/data_web_page.h" +#include "lang/lang_tag.h" // FormatCountDecimal. #include "ui/item_text_options.h" #include "ui/chat/chat_style.h" #include "ui/chat/message_bubble.h" #include "ui/effects/spoiler_mess.h" #include "ui/image/image_prepare.h" +#include "ui/cached_round_corners.h" +#include "ui/painter.h" #include "ui/power_saving.h" +#include "ui/text/text_utilities.h" #include "core/ui_integration.h" #include "styles/style_chat.h" @@ -202,6 +207,72 @@ QSize Media::countCurrentSize(int newWidth) { return QSize(qMin(newWidth, maxWidth()), minHeight()); } +bool Media::hasPurchasedTag() const { + if (const auto media = parent()->data()->media()) { + if (const auto invoice = media->invoice()) { + if (invoice->isPaidMedia && !invoice->extendedMedia.empty()) { + const auto photo = invoice->extendedMedia.front()->photo(); + return !photo || !photo->extendedMediaPreview(); + } + } + } + return false; +} + +void Media::drawPurchasedTag( + Painter &p, + QRect outer, + const PaintContext &context) const { + const auto purchased = parent()->enforcePurchasedTag(); + if (purchased->text.isEmpty()) { + const auto item = parent()->data(); + const auto media = item->media(); + const auto invoice = media ? media->invoice() : nullptr; + const auto amount = invoice ? invoice->amount : 0; + if (!amount) { + return; + } + const auto session = &item->history()->session(); + auto text = Ui::Text::Colorized(Ui::CreditsEmojiSmall(session)); + text.append(Lang::FormatCountDecimal(amount)); + purchased->text.setMarkedText(st::defaultTextStyle, text, kMarkupTextOptions, Core::MarkedTextContext{ + .session = session, + .customEmojiRepaint = [] {}, + }); + } + + const auto st = context.st; + const auto sti = context.imageStyle(); + auto right = outer.x() + outer.width(); + auto top = outer.y(); + right -= st::msgDateImgDelta + st::msgDateImgPadding.x(); + top += st::msgDateImgDelta + st::msgDateImgPadding.y(); + + const auto size = QSize( + purchased->text.maxWidth(), + st::normalFont->height); + const auto tagX = right - size.width(); + const auto tagY = top; + const auto tagW = size.width() + 2 * st::msgDateImgPadding.x(); + const auto tagH = size.height() + 2 * st::msgDateImgPadding.y(); + Ui::FillRoundRect( + p, + tagX - st::msgDateImgPadding.x(), + tagY - st::msgDateImgPadding.y(), + tagW, + tagH, + sti->msgDateImgBg, + sti->msgDateImgBgCorners); + + p.setPen(st->msgDateImgFg()); + purchased->text.draw(p, { + .position = { tagX, tagY }, + .outerWidth = width(), + .availableWidth = tagW - 2 * st::msgDateImgPadding.x(), + .palette = &st->priceTagTextPalette(), + }); +} + void Media::fillImageShadow( QPainter &p, QRect rect, diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index 9d577255c..94ad6520b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -357,6 +357,12 @@ public: return false; } + [[nodiscard]] bool hasPurchasedTag() const; + void drawPurchasedTag( + Painter &p, + QRect outer, + const PaintContext &context) const; + virtual ~Media() = default; protected: diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 16d9d8080..4b26222ee 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -133,6 +133,8 @@ GroupedMedia::Mode GroupedMedia::DetectMode(not_null media) { } QSize GroupedMedia::countOptimalSize() { + _purchasedPriceTag = hasPurchasedTag(); + std::vector sizes; const auto partsCount = _parts.size(); sizes.reserve(partsCount); @@ -433,7 +435,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { if (!part.cache.isNull()) { nowCache = true; } - if (unpaid) { + if (unpaid || _purchasedPriceTag) { fullRect = fullRect.united(part.geometry); } } @@ -445,6 +447,8 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { unpaid->drawPriceTag(p, fullRect, context, [&] { return generatePriceTagBackground(fullRect); }); + } else if (_purchasedPriceTag) { + drawPurchasedTag(p, fullRect, context); } // date diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h index b101fef91..5397b6817 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h @@ -155,7 +155,8 @@ private: mutable std::optional _captionItem; std::vector _parts; Mode _mode = Mode::Grid; - bool _needBubble = false; + bool _needBubble : 1 = false; + bool _purchasedPriceTag : 1 = false; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 2485552d8..043883035 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -139,6 +139,7 @@ void Photo::create(FullMsgId contextId, PeerData *chat) { if (_spoiler) { createSpoilerLink(_spoiler.get()); } + _purchasedPriceTag = hasPurchasedTag() ? 1 : 0; } void Photo::ensureDataMediaCreated() const { @@ -393,6 +394,14 @@ void Photo::draw(Painter &p, const PaintContext &context) const { p.drawRoundedRect(rect, radius, radius); sti->historyPageEnlarge.paintInCenter(p, rect); } + if (_purchasedPriceTag) { + auto geometry = rthumb; + if (showEnlarge) { + const auto rect = enlargeRect(); + geometry.setY(rect.y() + rect.height()); + } + drawPurchasedTag(p, geometry, context); + } // date if (!inWebPage && (!bubble || isBubbleBottom())) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index 223eae37e..3c1aec3e4 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -167,7 +167,8 @@ private: const std::unique_ptr _spoiler; mutable QImage _imageCache; mutable std::optional _imageCacheRounding; - uint32 _serviceWidth : 28 = 0; + uint32 _serviceWidth : 27 = 0; + uint32 _purchasedPriceTag : 1 = 0; mutable uint32 _imageCacheForum : 1 = 0; mutable uint32 _imageCacheBlurred : 1 = 0; mutable uint32 _pollingStory : 1 = 0; diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 1518b69b1..acfde082f 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -44,3 +44,6 @@ creditsBoxAboutDivider: FlatLabel(boxDividerLabel) { creditsBoxButtonLabel: FlatLabel(defaultFlatLabel) { style: semiboldTextStyle; } + +starIconSmall: icon{{ "payments/small_star", windowFg }}; +starIconSmallPadding: margins(0px, -3px, 0px, 0px); From 1988435cdf7b260b136510ba2ac8ff433402b536 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 25 Jun 2024 18:00:28 +0400 Subject: [PATCH 117/142] Add a Windows Hello / Touch ID system unlock. --- .../Resources/icons/menu/passcode_finger.png | Bin 0 -> 911 bytes .../icons/menu/passcode_finger@2x.png | Bin 0 -> 1867 bytes .../icons/menu/passcode_finger@3x.png | Bin 0 -> 2736 bytes .../icons/menu/passcode_winhello.png | Bin 0 -> 454 bytes .../icons/menu/passcode_winhello@2x.png | Bin 0 -> 772 bytes .../icons/menu/passcode_winhello@3x.png | Bin 0 -> 1135 bytes Telegram/Resources/langs/lang.strings | 8 ++ Telegram/SourceFiles/boxes/boxes.style | 22 ++++ Telegram/SourceFiles/core/core_settings.cpp | 10 +- Telegram/SourceFiles/core/core_settings.h | 8 ++ .../settings/settings_local_passcode.cpp | 49 +++++++- .../settings/settings_privacy_security.cpp | 3 + Telegram/SourceFiles/ui/menu_icons.style | 2 + .../window/window_lock_widgets.cpp | 110 ++++++++++++++++++ .../SourceFiles/window/window_lock_widgets.h | 15 +++ Telegram/lib_base | 2 +- cmake | 2 +- 17 files changed, 225 insertions(+), 6 deletions(-) create mode 100644 Telegram/Resources/icons/menu/passcode_finger.png create mode 100644 Telegram/Resources/icons/menu/passcode_finger@2x.png create mode 100644 Telegram/Resources/icons/menu/passcode_finger@3x.png create mode 100644 Telegram/Resources/icons/menu/passcode_winhello.png create mode 100644 Telegram/Resources/icons/menu/passcode_winhello@2x.png create mode 100644 Telegram/Resources/icons/menu/passcode_winhello@3x.png diff --git a/Telegram/Resources/icons/menu/passcode_finger.png b/Telegram/Resources/icons/menu/passcode_finger.png new file mode 100644 index 0000000000000000000000000000000000000000..ee267f708913533de52430b6ad43beb170d9e187 GIT binary patch literal 911 zcmV;A191F_P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR917@z|H1ONa40RR917ytkO0OB=utpET5`AI}UR5%gclv^mfaTv$Vq&X!` zOeV)-Noj=~;zCYI4wEP)D}`ujh2(G{H?+Hz(k_TxaX}=dxUivx8o3!cmdFxj4w3(- zUjNnqwST+!Uc7tu9KOHr?|Ht@>*MolABo4C$>s8vmX^G{yriV0*x1;or>Co{tM&Eu z`T2Q^#p1C<3=|w3JT^Ag)YN3R+vn!yPEJnF&(CotBqWrSl$4c~ZEkMrbh^XCLyvIZ zPO=XU4t95U3knJZS9o}MT3T9MT%1%Y#S;?~Gc`5!{QO*BU;kgAsHmvh+uPaM*?@om zl4@#d0R8&=+}+(742GbfAVST}&2MjS6%`epv6h#YS65ewg@lCAi^s>u-rnBK%*?>R zz{JGFy1Kf(y}i@Z)7;!#aymOZNl+*hVyx=w>ihfq=;&wylarI&j$2@JBTxj6*drq& zH#avC5fOwoHa1KqlNcyHJv}=+8*f!r6*Hn(B9U}=cVlz8kB^U;nVGP#FcKiXu&{tp zSXk(EI^iqE68^8PtuZz1uC6W)FE1~L4*fQp%_}P__)}9;frc*x9S#R`^bEuWH5v`E zv$M0t#zqXaTFpB$GV=5DGdVdKrVkGfm6eq^x3{<3+uJdCe1#wPoAc40pPwH(WNI*{ zUrMDCc1K4?Sy@@o86O|NzP_e}+4=kXyKDb3j*pKSJT7P#7Z(c#Qp7MCjfmUV_xo&> zO7-&cl97=i)QcHiTwKJ*#}k7{ettd%t<`F^7(+ut(A?SC>FMbi9UWy?CMG7VR;vIc ze)sqHZ*6ViZEbBuH84*}NpZPc=m;mfLc-G0()9GSKwMn!{!cp?HlvZrWU%Y&>mx+h zUteDb2M0q#L*1F)4R)0?$tx`_rJcgbMMXt6n+@S`wZ+9n)Cf~ASga`W@95|tkB`002ovPDHLkV1j4EmVE#K literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/passcode_finger@2x.png b/Telegram/Resources/icons/menu/passcode_finger@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a0a34240bfb0ebe7578aab0d18206289a7a2e7c2 GIT binary patch literal 1867 zcmV-R2ekN!P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NIwMj%lR9Fe^mwQaleH6#L2$fta z6muUFH4&zjP!m&_F>~9RVcMuzYt}HcW%x%fV{@4`mOC3|Z8l*ubA0yeQQx0u?fIwgAK%~ky}#$2&-tGF*TJFV>hM5^2in&Ij(?zj^ytx(Cr_R* zVZxX(W4yh+Jv}`;ckcY^)vK33ud=eTtgNh@oSd&;zy5)8J485p!-fs_?%n%g;i|2z zJ#*&F)TvYLIN&F%B@gxJ(IYW2aoxIg9v&WYz_)MTN=r-g^75WPfBx>>yQZe5zJ2@p z`1the*Kg9KN%&0dxOVN@=FOXL-n^;EegsAS ztE8mFE^*s~TwGjEpFSNnirKSgljbem(6wvV{rmTe zo{t|tPMH-4;%}17KGC&SEdGaK#Qkm}EyYJYsqoJX}GyxMPf*n{F)_o350@}eS+u3~>(^hueqD{r z%*^cBv!`l^ii#4`Z{NOc+NuoyLg4BrPoALh@#Dv4^@fFokpbY?dgREF)?bmzSF;{I zd`O7(B+^{WUA}y|f~^r7H*OSx$jC_L8!=+U+qZAQKYaKwZ{9q`TOr88ty{N>r|;ak zSwJfnU zYt|shg;P~9I5=1g<>eLpEpg+<4V-L7Bq1R|lv3WN7-ONRs7M~koy0nL@SvGA@bBBV zkEd8LZrnH(&&bFCL|hT81S!R2K$!GY89LtK!-vHPxyu_1O)O^3nx)LRO&UEDS3$af zFx%ZK=gyrgl2nkYOHWT1@c#Y#^XJbWFkk>C;((Nt6r98(?les0kB^TBq`nx>0+KzA zjg5etHf>TiOT@8b#}H$VDmQNr!i8C8ixLKZe}CpYPq9^7235u|2Be{@o~u`{f}A~j zRuM}?VPPR+yLazaZafe2=+Ps0cXtI%f&_j(W6H}Zr!@2h!9RT z|84&E@82J!qM|}@idI&fbLY-cbONqixpLL2RVv6!fBN)^zto+AqCvC?NI658h~RA) zNAgkxDy@BZ`f@87+9_YN5{1cGpzc-yp; zJ)aQD$`GJBSlJZOHp4=#pbib<$ZV0!Bi6L5?CfkbaGgJYUiqyONl8ga^16KJhz#0) zYWtzBlBMAD=g)v7E2pUS4i4QnY@ZxK^!!Z~XZ2a=bPb!cTev?%cUk6&*Nm z0EB$E*-*)A=|G}H?quPTB}-J+K7u@8_d^kdt&+!&9|z%eHAW-~BN>nk3kV2MP6#xx zD-u=)3;v!tbEbsPP)WoDUHaw=bEa8j6@YTp@laZ_T=&CQ&& zefxG56f21l(^o+XO$He_8&zm%s6MhzI9g#PM$48hV?NVYr2CP``T6!7;B3m|JsOqPN@&5h$@bGZOAux+Io)v|*DRvV_Ql72DChJWpJ80&j zEazmdImTJgqzPdeedu?s1#!Bf9p_nL9X4zjV;?_}xnswURS}kU{eWj?B_}8M>eWl1 zwlBfgnfQa|rURcNu^=riO%sKtKx!{qw5WZp5$$}}5;YP+rlWZzt?0sq3p`TG7A{<9 zGPD=XUSWRI*c^kQbmZLJT*7BY)`qR)Z-)muJn(;c;4j0;<#Z1Y5DEYQ002ovPDHLk FV1i&-h&uoP literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/passcode_finger@3x.png b/Telegram/Resources/icons/menu/passcode_finger@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..291fc8eaff15153de56f6f98e6dc240e1e9f888b GIT binary patch literal 2736 zcmV;h3QzTkP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS_8c9S!RA>e5nq{oiyAr_f!QI^* zg1ZM2+}-&A1b0Yqx1hlS1cC-a@L)j`+}$l)+#V3z{k>mqb0^L8zsv64bIyGqc0cS+ zJJacO+KzSq_#^xYYapzFum-{!2x}m$f&V)VL`1qo$&w{2S+ZoQQl(0iD3LB*y5z}| zr$~{4ZzlTi;lum)@1Hz*^56N3f~%9=H6WJ>eTdBu$zw|n>Qr%s&;crD(9sAJ#0eU&R${^ueY4xt~#Sg~St z=+I&0$dQ>dXZDol^XJdEZ{L3P>ecJlulbIMh)9$uQPQMIvuDqqEn7B*dyd+>ckifC zqd20T*eLI;SFhf!Ten>0zkK;}$&w|VI&~^iq=+xk5e`)G;>Aad7{LO5{`~3k*|~FP z(V|78d>Qrx2@)ijKYzXw<;#~Zt5>hCU%!6r*s*QmK;V=qQ?_l}_P~Jyis#p_U%Ped z76^w(ph3+yZrrdX;Px+ExX{;W3~I_MRjM30a>Qm1ykfeZ`bn(c`< zZQ7J0M~Z{MX$ zmwK@LyDN3-)WwPwOP@YH-i+^AidR76j&} zQl&~)u3V9x$N^fnZvErO57FYebLaeQRKbD;hYlU;%PI(y3l}boA3wfKnKHgaY}v9U z-dyM(Ja~{HLk3^Oq4bw7T^idU6C65pC`pneo{YGGJ9g|4$^e1m$B%QnJQ?Q8m+$7y zo5BZOBu<>zlQQVeG-=X2eE3jUZ{NP%^D5())~{dhC+5JWV`hYp3*f%+5UzYgB!pl> zzkBy?KVHNg?%l(!IC$`&Fh#X;$Ls+$Yu3bl37G+&KYzY_`SMn+TA6DGs~9n2pr*ow z3pZ-i2#3MZQbTa)?b@}oIU5*<_U_#~A)YmBmP->{Pp@9RgdKO@J>mot!-fr09`^9z z!_}%)V;R4lj6iwo)~zyy0p`w~>pDYB<+pF&3<1-XFJJz*%zY9NlFL4DiYip7;F3<8 zHq936+O=!!o=fJx2m67gvH5VSQJEUmzkh$BVWQ9vB$^XOGiT0J)6neNwW|;StX;d7 zP|zg{qz5UP!sa$*$`mya8qB@WbnMts5d_ra%8GsGwvb|}VeE_$FxikHLli*>O#&;M zXXnnH6_ct}tIERe-Mi;`;nfh`?9ro#u;|jIi$W(%nDFuA$3if5>QqG_O#;h0b?V^C zCrz4!*McpHiJwzM1|)1hefs1^Y8y9h6ao|>69o#iZ{OaG-@kvqYX%TexUXBc&b<#z zQ>aiOt_p@uCWvoPcvh2`hy1#R?KO#pV$UN0~0qi*|lqzU`g#<4k7l;m@&if z<(s3WxUf!AeIXEl%OjeWP5^e*J(;y?)e@S`n>VW|nm2DQ*q%LmDs1S@`}gk~z63kC zU7Hbm#qp3?0f^Jvv}r>G%z1_w1(Raky?fWDS+r=8&BVZa_Uth<9A;%~iK@h>ZP~KL z9UDpyE>&b2J9eyMF>c&A!4kh>Gt|lrJ;$4iWJg#8D2kO_au^Z~E3CvrMEwdCViV&g zqGKLxF81B~`0*p5vyFA(7A;!HJ>(R`ix(i%?!g@CC5<|0Q%@mCk3rOA+ijY#~!nrAIC{4+2nN^J^r!bQ}d-lw=2a4D< zQMOo=RjXDh8nRf!kP4SV5i82VJmY5Qe;=yaOfzQ8Xqqbj#WoZuP{5!_$*_U;C=4k( z?8ar}BWKQ>F_h|H(@6|qP#jB@E8z)+zpvylh=n0Nd57Uhk}s?Q=$Bo(bLWnYBKstM z&5|WczkdBFecLEtA zSbdQN*H36?gdn+ABc(pY*|V=4WCZDE6au-WPdini%uEqdakiUz^5ns88E$;nty|Z| zh7OM&JzAKG={M{qOqd`j^%~T)X;VQ>o;+Dij~33KKQ9CvJW@(|XIsXbBXFb7GdpEz-%%?KYV>g3YfC}2CftUAfsDVgD6#V9&C9n;^?`xMydCXK#R=1iR$9xG(6bmf_-5IzzBWLLh+x_7E%TmeXW9 z>=5c8gy(|sp^0j^dsGlEUAja(DU?wT*l!4w^4&e5qC~%~e*PWe-!(_M(1A=qg~PUQ z98Q%%!-UY8u-IN3aU&sLX0HvH(@QzAVZ(-U zRs)|jnkb2k?090sh7Fz*^G1$CSIeVGp`Y4@Tb%P`65XA=S4cKV7U*kw92y01&*fn! zc+vMY@ZanwoX2n>pHz{2Nkb$}X`0KaJeX^Hd~t~Ge%y3WAs-P8uT3}!9)yUkqg&kY qd|?fQH4xT7SOZ}Vgf$So8u%amqw%uhS{&&B0000d@Gu>r!h|1{Q`3Z1GjhXnk!CM{5(?^YC2iX zzB}L7*zn}0tD9VxRlTo1pZKi2Z1?vne}=gX=ReoppYNw08XCwarr;9b@Y7qC<>2%0 zR&xCroNs!6@9Y0GJt}ZYYUH}>uOAjTcqcHP`^>{S<^PgGnfvd*_wzOg*nX<_%b4Xi zspavG`*S9zMrs6$d|lF$7!exk8YsZ>QN>NbWV6od*HSD^f}H#;bzU;ZRXmk`_#N7? zSYua=-XxW~tkTJ|o|*XO-j15KwevvMEH-z$`RBLg=3naXWj}4;Bhl9PxFmM^vX>Tp zuXlAV;)s@RoA`X^>s=p?n9JMUd~t0;>dntKvDaB08&>ijzF@)|vr6amS+V@z&xL<5 Xol7k?b$Rq!5EO=u{ z=}kM;&l4$Ndf#~Nu9tTT_q{h-{qj!hx$1k*E6-KW`#rDt?wt?q)(2!C@D;G{V7|ln z?!a{BqMbH#{rZnTzL_)a^wW3m-{0JqVIp;afsNJW!p(}4ksWpOY^Hj(>M|bS{3Bdq z7FSeeG;@w#!^XJv_RE_@SlRx{%$W7x`RH?}sa~#2j~FvKtPSH%HcDvFeDc37qvNtK zKO3`|a6_=oi<~`AbY_KQaO3^9=E?MSnc zn|_9UVT6uQ-S*piKfUhw@BFlA=8DkOSG`v;KL7S@+vS%6Q>RZCpMAD$=7fU@8C#=V z(k*1Nw%$5jvM<7FF5gmtwQtL$yN@o(Vr6RFzTJG$hhM*b{rI5~+IZ~o-RoJVi#pM7FM5fHE(-i!`d+IccNbW`Wix= zQ@m7*7I3J}KY#!3-M|ys&b#l%t-szn>qNrln>P0L=Cgg5T=;$Y6gA`6MbbAj}e$&&%F(iZa z?abTSHGv|>C8rm5dg$?Y1-*MbZ_*@{dM5rwfn6g181yGsOcb9~A@($MPrxLVnIE-3 zI(Zy9EwEGd$ottV@0OiAWA}T`=E{V*?Jwu-z5Z*>*8S=C&&}Ilu*-uns6j-56)W|x zhbb~L(%9HoU0ppqJpAKCwKdBP}ic^(Kqgv17-c z96n$n!VVOa@@{_?`S0S>qE;iZke9J6$C|Wk-Gh_%CO&`uTuQ9k#@aghdz(2^vLlD{ zn$qT;o}S~!k1yD}V(Zq>dEAc|2pkY}nz#7UruFOn=L@IhZZ>?d=s82)w8fY9?cMwL z*DtFHVrttze*Cy&hs9>b2Tbl}=1d%GB0H+8+YbkZgoFeKColA@F)Lta(O~#lv`ZZp z8vo`o2^#sHJAXbuGjr$u{l~@E_53xzDIy|bVs5VOt*o!Fzv1RlpxjGy)!YtUP0h-x zszdj9_j{bYaPP)~-MeSM4cNYYyRx!!Z*53)!sM+RHy*t7;?=7l0Y!83?_a-8b+nc1 zXIkOuQeSWH%}||cn$Kei^w)=&oKRo2w9_`~>h1qO7?-|F5V-NOo3_Qq#H6IB zOaIVV<>%#f>;C=ph5h~gj~_o4-Dq!k^yty^=g;dLc=#|eDr(j#hJz6oFJ4@|dbQ4r z-@mJCYikXht5g5zG$mBD&zw2)+XSWjnt8f9Ivc_srd3o#JV{udS5PpazofDRI;l@AXmSI(S0 zdvnc^nKNc|2-lS99XWCN@?}B6px<_m+n%j@_3G7!^BkuY3Ya&BhlO2uGi8d1q|7~g zwOuP$uADtvn)lVihYOc4cV`p2a_m}x3%{h4)FH+Nbv+MRtM=ZjpMU+}LB>zVT3MN2 z2Ne|=`8jE8X=!O|Z@jjA`SRDs(ed%`U%Y52__b=j_Q4|^i3={?{-hk{A){6Io+ZO; z_NGmne*UaHd%*bWp1phbZriqPzFgp>UKju3-FLmcz5kfG-mxhO`I7!7N5$D=)=jD8 zCW*knK#R*q9klfIk9Qo};WRrrDJd^MpZPIw?78Qk-@bYC<8K{f`zkDX>&IHIKkPOW Wc0?^!*|!ap**#tTT-G@yGywnyul6Va literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 80441cd03..01a1e7cf5 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -683,6 +683,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_privacy_premium_link" = "Telegram Premium"; "lng_settings_passcode_disable" = "Disable Passcode"; "lng_settings_passcode_disable_sure" = "Are you sure you want to disable passcode?"; +"lng_settings_use_winhello" = "Unlock with Windows Hello"; +"lng_settings_use_winhello_about" = "You need to enter your passcode once before unlocking Telegram with Windows Hello."; +"lng_settings_use_touchid" = "Unlock with Touch ID"; +"lng_settings_use_touchid_about" = "You need to enter your passcode once before unlocking Telegram with Touch ID."; "lng_settings_password_disable" = "Disable Cloud Password"; "lng_settings_password_abort" = "Abort two-step verification setup"; "lng_settings_about_bio" = "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco"; @@ -992,6 +996,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passcode_ph" = "Your passcode"; "lng_passcode_submit" = "Submit"; "lng_passcode_logout" = "Log out"; +"lng_passcode_winhello" = "You need to enter your passcode\nbefore you can use Windows Hello."; +"lng_passcode_touchid" = "You need to enter your passcode\nbefore you can use Touch ID."; +"lng_passcode_winhello_unlock" = "Telegram wants to unlock with Windows Hello."; +"lng_passcode_touchid_unlock" = "unlock"; "lng_passcode_create_button" = "Save Passcode"; "lng_passcode_check_button" = "Submit"; "lng_passcode_change_button" = "Save Passcode"; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index f17389f1b..364d8f289 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -290,6 +290,28 @@ passcodeTextLine: 28px; passcodeLittleSkip: 5px; passcodeAboutSkip: 7px; passcodeSkip: 23px; +passcodeSystemUnlock: IconButton(defaultIconButton) { + width: 32px; + height: 32px; + rippleAreaSize: 24px; + rippleAreaPosition: point(4px, 4px); + ripple: RippleAnimation(defaultRippleAnimation) { + color: lightButtonBgOver; + } +} +passcodeSystemWinHello: IconButton(passcodeSystemUnlock) { + icon: icon{{ "menu/passcode_winhello", lightButtonFg }}; + iconOver: icon{{ "menu/passcode_winhello", lightButtonFg }}; +} +passcodeSystemTouchID: IconButton(passcodeSystemUnlock) { + icon: icon{{ "menu/passcode_finger", lightButtonFg }}; + iconOver: icon{{ "menu/passcode_finger", lightButtonFg }}; +} +passcodeSystemUnlockLater: FlatLabel(defaultFlatLabel) { + align: align(top); + textFg: windowSubTextFg; +} +passcodeSystemUnlockSkip: 12px; newGroupAboutFg: windowSubTextFg; newGroupPadding: margins(4px, 6px, 4px, 3px); diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index daaff5acf..efd0ba4d5 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -220,7 +220,7 @@ QByteArray Settings::serialize() const { + Serialize::bytearraySize(ivPosition) + Serialize::stringSize(noWarningExtensions) + Serialize::stringSize(_customFontFamily) - + sizeof(qint32); + + sizeof(qint32) * 2; auto result = QByteArray(); result.reserve(size); @@ -371,7 +371,8 @@ QByteArray Settings::serialize() const { << qint32(std::clamp( qRound(_dialogsNoChatWidthRatio.current() * 1000000), 0, - 1000000)); + 1000000)) + << qint32(_systemUnlockEnabled ? 1 : 0); } Ensures(result.size() == size); @@ -491,6 +492,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { qint32 ttlVoiceClickTooltipHidden = _ttlVoiceClickTooltipHidden.current() ? 1 : 0; QByteArray ivPosition; QString customFontFamily = _customFontFamily; + qint32 systemUnlockEnabled = _systemUnlockEnabled ? 1 : 0; stream >> themesAccentColors; if (!stream.atEnd()) { @@ -788,6 +790,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) { 0., 1.); } + if (!stream.atEnd()) { + stream >> systemUnlockEnabled; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for Core::Settings::constructFromSerialized()")); @@ -995,6 +1000,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { _ivPosition = Deserialize(ivPosition); } _customFontFamily = customFontFamily; + _systemUnlockEnabled = (systemUnlockEnabled == 1); } QString Settings::getSoundPath(const QString &key) const { diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index 2588923c6..3412c4588 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -884,6 +884,13 @@ public: _customFontFamily = value; } + [[nodiscard]] bool systemUnlockEnabled() const { + return _systemUnlockEnabled; + } + void setSystemUnlockEnabled(bool enabled) { + _systemUnlockEnabled = enabled; + } + [[nodiscard]] static bool ThirdColumnByDefault(); [[nodiscard]] static float64 DefaultDialogsWidthRatio(); @@ -1014,6 +1021,7 @@ private: rpl::variable _ttlVoiceClickTooltipHidden = false; WindowPosition _ivPosition; QString _customFontFamily; + bool _systemUnlockEnabled = false; bool _tabbedReplacedWithInfo = false; // per-window rpl::event_stream _tabbedReplacedWithInfoValue; // per-window diff --git a/Telegram/SourceFiles/settings/settings_local_passcode.cpp b/Telegram/SourceFiles/settings/settings_local_passcode.cpp index 9a766f16d..db21f717b 100644 --- a/Telegram/SourceFiles/settings/settings_local_passcode.cpp +++ b/Telegram/SourceFiles/settings/settings_local_passcode.cpp @@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_local_passcode.h" #include "base/platform/base_platform_last_input.h" +#include "base/platform/base_platform_info.h" +#include "base/system_unlock.h" #include "boxes/auto_lock_box.h" #include "core/application.h" #include "core/core_settings.h" @@ -23,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/fields/password_input.h" #include "ui/widgets/labels.h" #include "ui/wrap/vertical_layout.h" +#include "ui/wrap/slide_wrap.h" #include "window/window_session_controller.h" #include "styles/style_boxes.h" #include "styles/style_layers.h" @@ -199,11 +202,11 @@ void LocalPasscodeEnter::setupContent() { content, object_ptr( content, - isCreate + (isCreate ? tr::lng_passcode_create_button() : isCheck ? tr::lng_passcode_check_button() - : tr::lng_passcode_change_button(), + : tr::lng_passcode_change_button()), st::changePhoneButton)), st::settingLocalPasscodeButtonPadding)->entity(); button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); @@ -239,6 +242,8 @@ void LocalPasscodeEnter::setupContent() { } SetPasscode(_controller, newText); if (isCreate) { + Core::App().settings().setSystemUnlockEnabled(true); + Core::App().saveSettingsDelayed(); _showOther.fire(LocalPasscodeManageId()); } else if (isChange) { _showBack.fire({}); @@ -506,6 +511,46 @@ void LocalPasscodeManage::setupContent() { divider->skipEdge(Qt::BottomEdge, shown); }, divider->lifetime()); + const auto systemUnlockWrap = content->add( + object_ptr>( + content, + object_ptr(content)) + )->setDuration(0); + const auto systemUnlockContent = systemUnlockWrap->entity(); + + Ui::AddSkip(systemUnlockContent); + + AddButtonWithIcon( + systemUnlockContent, + (Platform::IsWindows() + ? tr::lng_settings_use_winhello() + : tr::lng_settings_use_touchid()), + st::settingsButton, + { Platform::IsWindows() + ? &st::menuIconWinHello + : &st::menuIconTouchID } + )->toggleOn( + rpl::single(Core::App().settings().systemUnlockEnabled()) + )->toggledChanges( + ) | rpl::filter([=](bool value) { + return value != Core::App().settings().systemUnlockEnabled(); + }) | rpl::start_with_next([=](bool value) { + Core::App().settings().setSystemUnlockEnabled(value); + Core::App().saveSettingsDelayed(); + }, systemUnlockContent->lifetime()); + + Ui::AddSkip(systemUnlockContent); + + Ui::AddDividerText( + systemUnlockContent, + (Platform::IsWindows() + ? tr::lng_settings_use_winhello_about() + : tr::lng_settings_use_touchid_about())); + + using namespace rpl::mappers; + systemUnlockWrap->toggleOn(base::SystemUnlockStatus( + ) | rpl::map(_1 == base::SystemUnlockAvailability::Available)); + Ui::ResizeFitChild(this, content); } diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp index ab8f32217..daea2e9ee 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_premium.h" // Settings::ShowPremium. #include "settings/settings_privacy_controllers.h" #include "settings/settings_websites.h" +#include "base/system_unlock.h" #include "base/timer_rpl.h" #include "boxes/passcode_box.h" #include "boxes/sessions_box.h" @@ -1079,6 +1080,8 @@ PrivacySecurity::PrivacySecurity( not_null controller) : Section(parent) { setupContent(controller); + + [[maybe_unused]] auto preload = base::SystemUnlockStatus(); } rpl::producer PrivacySecurity::title() { diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index 8d08ff7de..ce1670203 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -155,6 +155,8 @@ menuIconTagRename: icon{{ "menu/tag_rename", menuIconColor }}; menuIconGroupsHide: icon {{ "menu/hide_members", menuIconColor }}; menuIconFont: icon {{ "menu/fonts", menuIconColor }}; menuIconFactcheck: icon {{ "menu/factcheck", menuIconColor }}; +menuIconWinHello: icon {{ "menu/passcode_winhello", menuIconColor }}; +menuIconTouchID: icon {{ "menu/passcode_finger", menuIconColor }}; menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }}; menuIconTTLAnyTextPosition: point(11px, 22px); diff --git a/Telegram/SourceFiles/window/window_lock_widgets.cpp b/Telegram/SourceFiles/window/window_lock_widgets.cpp index 58f4881fa..e6453fbf2 100644 --- a/Telegram/SourceFiles/window/window_lock_widgets.cpp +++ b/Telegram/SourceFiles/window/window_lock_widgets.cpp @@ -7,6 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/window_lock_widgets.h" +#include "base/platform/base_platform_info.h" +#include "base/call_delayed.h" +#include "base/system_unlock.h" #include "lang/lang_keys.h" #include "storage/storage_domain.h" #include "mainwindow.h" @@ -27,6 +30,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_boxes.h" namespace Window { +namespace { + +constexpr auto kSystemUnlockDelay = crl::time(1000); + +} // namespace LockWidget::LockWidget(QWidget *parent, not_null window) : RpWidget(parent) @@ -99,6 +107,108 @@ PasscodeLockWidget::PasscodeLockWidget( _logout->setClickedCallback([=] { window->showLogoutConfirmation(); }); + + using namespace rpl::mappers; + if (Core::App().settings().systemUnlockEnabled()) { + _systemUnlockAvailable = base::SystemUnlockStatus() + | rpl::map(_1 == base::SystemUnlockAvailability::Available); + if (Core::App().domain().started()) { + _systemUnlockAllowed = _systemUnlockAvailable.value(); + setupSystemUnlock(); + } else { + setupSystemUnlockInfo(); + } + } +} + +void PasscodeLockWidget::setupSystemUnlockInfo() { + const auto info = Ui::CreateChild( + this, + (Platform::IsWindows() + ? tr::lng_passcode_winhello() + : tr::lng_passcode_touchid()), + st::passcodeSystemUnlockLater); + _logout->geometryValue( + ) | rpl::start_with_next([=](QRect logout) { + info->resizeToWidth(width() + - st::boxRowPadding.left() + - st::boxRowPadding.right()); + info->moveToLeft( + st::boxRowPadding.left(), + logout.y() + logout.height() + st::passcodeSystemUnlockSkip); + }, info->lifetime()); + info->showOn(_systemUnlockAvailable.value()); +} + +void PasscodeLockWidget::setupSystemUnlock() { + windowActiveValue() | rpl::skip(1) | rpl::filter([=](bool active) { + return active + && !_systemUnlockSuggested + && !_systemUnlockCooldown.isActive(); + }) | rpl::start_with_next([=](bool) { + [[maybe_unused]] auto refresh = base::SystemUnlockStatus(); + suggestSystemUnlock(); + }, lifetime()); + + const auto button = Ui::CreateChild( + _passcode.data(), + (Platform::IsWindows() + ? st::passcodeSystemWinHello + : st::passcodeSystemTouchID)); + button->showOn(_systemUnlockAllowed.value()); + _passcode->sizeValue() | rpl::start_with_next([=](QSize size) { + button->moveToRight(0, size.height() - button->height()); + }, button->lifetime()); + button->setClickedCallback([=] { + const auto delay = st::passcodeSystemUnlock.ripple.hideDuration; + base::call_delayed(delay, this, [=] { + suggestSystemUnlock(); + }); + }); +} + +void PasscodeLockWidget::suggestSystemUnlock() { + InvokeQueued(this, [=] { + if (_systemUnlockSuggested) { + return; + } + _systemUnlockCooldown.cancel(); + + using namespace base; + _systemUnlockAllowed.value( + ) | rpl::filter( + rpl::mappers::_1 + ) | rpl::take(1) | rpl::start_with_next([=] { + const auto weak = Ui::MakeWeak(this); + const auto done = [weak](SystemUnlockResult result) { + crl::on_main([=] { + if (const auto strong = weak.data()) { + strong->systemUnlockDone(result); + } + }); + }; + SuggestSystemUnlock( + this, + (::Platform::IsWindows() + ? tr::lng_passcode_winhello_unlock(tr::now) + : tr::lng_passcode_touchid_unlock(tr::now)), + done); + }, _systemUnlockSuggested); + }); +} + +void PasscodeLockWidget::systemUnlockDone(base::SystemUnlockResult result) { + if (result == base::SystemUnlockResult::Success) { + Core::App().unlockPasscode(); + return; + } + _systemUnlockCooldown.callOnce(kSystemUnlockDelay); + _systemUnlockSuggested.destroy(); + if (result == base::SystemUnlockResult::FloodError) { + _error = tr::lng_flood_error(tr::now); + _passcode->setFocusFast(); + update(); + } } void PasscodeLockWidget::paintContent(QPainter &p) { diff --git a/Telegram/SourceFiles/window/window_lock_widgets.h b/Telegram/SourceFiles/window/window_lock_widgets.h index d2967d47f..8c48850fb 100644 --- a/Telegram/SourceFiles/window/window_lock_widgets.h +++ b/Telegram/SourceFiles/window/window_lock_widgets.h @@ -11,6 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" #include "ui/layers/box_content.h" #include "base/bytes.h" +#include "base/timer.h" + +namespace base { +enum class SystemUnlockResult; +} // namespace base namespace Ui { class PasswordInput; @@ -61,15 +66,25 @@ protected: private: void paintContent(QPainter &p) override; + + void setupSystemUnlockInfo(); + void setupSystemUnlock(); + void suggestSystemUnlock(); + void systemUnlockDone(base::SystemUnlockResult result); void changed(); void submit(); void error(); + rpl::variable _systemUnlockAvailable = false; + rpl::variable _systemUnlockAllowed = false; object_ptr _passcode; object_ptr _submit; object_ptr _logout; QString _error; + rpl::lifetime _systemUnlockSuggested; + base::Timer _systemUnlockCooldown; + }; struct TermsLock { diff --git a/Telegram/lib_base b/Telegram/lib_base index 1be2a262a..b512eead3 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 1be2a262a6524d6dec3b616c2e9fd2ce42c9e61a +Subproject commit b512eead302cb7b509869778348d60fef64bc19b diff --git a/cmake b/cmake index b92244f0c..104406258 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit b92244f0c21f157600484498c33a3566087526dd +Subproject commit 104406258fa89f007aad69348cef9b433f128fc2 From b58c03f0de24cfd3ca5e23bbfcf12baabf0eb24a Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 25 Jun 2024 22:12:46 +0400 Subject: [PATCH 118/142] Fix build with Xcode. --- Telegram/SourceFiles/ui/effects/credits_graphics.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index 6cd783f89..e8290d56b 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -298,7 +298,6 @@ PaintRoundImageCallback GenerateCreditsPaintEntryCallback( rpl::single(rpl::empty_value()) | rpl::then( video->owner().session().downloaderTaskFinished() ) | rpl::start_with_next([=] { - using Size = Data::PhotoSize; if (const auto thumbnail = state->view->thumbnail()) { state->imagePtr = thumbnail; } @@ -421,7 +420,6 @@ PaintRoundImageCallback GeneratePaidMediaPaintCallback( Fn)> PaintPreviewCallback( not_null session, const Data::CreditsHistoryEntry &entry) { - using MediaType = Data::CreditsHistoryMediaType; const auto &extended = entry.extended; if (!extended.empty()) { return [=](Fn update) { From c833b8a1b064e1c1d20a44bd290b35978177abb2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 27 Jun 2024 10:24:32 +0400 Subject: [PATCH 119/142] Make purchased media badge better looking. --- .../history/view/media/history_view_media.cpp | 15 ++++++++------- Telegram/SourceFiles/ui/chat/chat.style | 1 + Telegram/SourceFiles/ui/effects/credits.style | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index 28e4e0870..ddd76f945 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -243,22 +243,23 @@ void Media::drawPurchasedTag( const auto st = context.st; const auto sti = context.imageStyle(); + const auto &padding = st::purchasedTagPadding; auto right = outer.x() + outer.width(); auto top = outer.y(); - right -= st::msgDateImgDelta + st::msgDateImgPadding.x(); - top += st::msgDateImgDelta + st::msgDateImgPadding.y(); + right -= st::msgDateImgDelta + padding.right(); + top += st::msgDateImgDelta + padding.top(); const auto size = QSize( purchased->text.maxWidth(), st::normalFont->height); const auto tagX = right - size.width(); const auto tagY = top; - const auto tagW = size.width() + 2 * st::msgDateImgPadding.x(); - const auto tagH = size.height() + 2 * st::msgDateImgPadding.y(); + const auto tagW = padding.left() + size.width() + padding.right(); + const auto tagH = padding.top() + size.height() + padding.bottom(); Ui::FillRoundRect( p, - tagX - st::msgDateImgPadding.x(), - tagY - st::msgDateImgPadding.y(), + tagX - padding.left(), + tagY - padding.top(), tagW, tagH, sti->msgDateImgBg, @@ -268,7 +269,7 @@ void Media::drawPurchasedTag( purchased->text.draw(p, { .position = { tagX, tagY }, .outerWidth = width(), - .availableWidth = tagW - 2 * st::msgDateImgPadding.x(), + .availableWidth = size.width(), .palette = &st->priceTagTextPalette(), }); } diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index a817f5dfe..27b9e36c1 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1144,3 +1144,4 @@ factcheckField: InputField(defaultInputField) { style: defaultTextStyle; } +purchasedTagPadding: margins(3px, 2px, 6px, 2px); diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index acfde082f..e78642348 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -46,4 +46,4 @@ creditsBoxButtonLabel: FlatLabel(defaultFlatLabel) { } starIconSmall: icon{{ "payments/small_star", windowFg }}; -starIconSmallPadding: margins(0px, -3px, 0px, 0px); +starIconSmallPadding: margins(0px, -2px, 0px, 0px); From 631d6abb06ffd85260513c56421dd124c98ea82d Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 27 Jun 2024 10:52:17 +0400 Subject: [PATCH 120/142] Improve formatting in message field. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 6ec776584..da1d618a7 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 6ec776584efd981ee2af3f231f4368ecf9161099 +Subproject commit da1d618a76a3b1bf652b110d6a1d7a03031d18b3 From 09e6077e974cbf47c299f7a448b6178a0eb83d0c Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 27 Jun 2024 16:55:19 +0400 Subject: [PATCH 121/142] Suggest global search of cashtags. --- .../dialogs/dialogs_inner_widget.cpp | 7 ++-- .../dialogs/dialogs_inner_widget.h | 3 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 20 ++++++----- Telegram/SourceFiles/dialogs/dialogs_widget.h | 3 +- .../SourceFiles/dialogs/ui/chat_search_in.cpp | 36 ++++++++++++------- .../SourceFiles/dialogs/ui/chat_search_in.h | 11 ++++-- 6 files changed, 52 insertions(+), 28 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index e10544c6d..fe5651774 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -2655,7 +2655,7 @@ void InnerWidget::applySearchState(SearchState state) { onHashtagFilterUpdate(QStringView()); } _searchState = std::move(state); - _searchingHashtag = IsHashtagSearchQuery(_searchState.query); + _searchHashOrCashtag = IsHashOrCashtagSearchQuery(_searchState.query); updateSearchIn(); moveSearchIn(); @@ -3332,7 +3332,8 @@ auto InnerWidget::searchTagsChanges() const } void InnerWidget::updateSearchIn() { - if (!_searchState.inChat && !_searchingHashtag) { + if (!_searchState.inChat + && _searchHashOrCashtag == HashOrCashtag::None) { _searchIn = nullptr; return; } else if (!_searchIn) { @@ -3381,7 +3382,7 @@ void InnerWidget::updateSearchIn() { ? Ui::MakeUserpicThumbnail(sublist->peer()) : nullptr; const auto myIcon = Ui::MakeIconThumbnail(st::menuIconChats); - const auto publicIcon = _searchingHashtag + const auto publicIcon = (_searchHashOrCashtag != HashOrCashtag::None) ? Ui::MakeIconThumbnail(st::menuIconChannel) : nullptr; const auto peerTabType = (peer && peer->isBroadcast()) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 3adad0cf4..033418af1 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -62,6 +62,7 @@ class IndexedList; class SearchTags; class SearchEmpty; class ChatSearchIn; +enum class HashOrCashtag : uchar; struct ChosenRow { Key key; @@ -514,7 +515,7 @@ private: Ui::DraggingScrollManager _draggingScroll; SearchState _searchState; - bool _searchingHashtag = false; + HashOrCashtag _searchHashOrCashtag = {}; History *_searchInMigrated = nullptr; PeerData *_searchFromShown = nullptr; Ui::Text::String _searchFromUserText; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 29e0400d7..913425a79 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -2125,14 +2125,14 @@ bool Widget::searchForPeersRequired(const QString &query) const { return _searchState.filterChatsList() && !_openedForum && !query.isEmpty() - && !IsHashtagSearchQuery(query); + && (IsHashOrCashtagSearchQuery(query) == HashOrCashtag::None); } bool Widget::searchForTopicsRequired(const QString &query) const { return _searchState.filterChatsList() && _openedForum && !query.isEmpty() - && !IsHashtagSearchQuery(query) + && (IsHashOrCashtagSearchQuery(query) == HashOrCashtag::None) && !_openedForum->topicsList()->loaded(); } @@ -2654,16 +2654,19 @@ void Widget::updateCancelSearch() { QString Widget::validateSearchQuery() { const auto query = currentSearchQuery(); if (_searchState.tab == ChatSearchTab::PublicPosts) { - _searchingHashtag = true; + if (_searchHashOrCashtag == HashOrCashtag::None) { + _searchHashOrCashtag = HashOrCashtag::Hashtag; + } const auto fixed = FixHashtagSearchQuery( query, - currentSearchQueryCursorPosition()); + currentSearchQueryCursorPosition(), + _searchHashOrCashtag); if (fixed.text != query) { setSearchQuery(fixed.text, fixed.cursorPosition); } return fixed.text; } else { - _searchingHashtag = IsHashtagSearchQuery(query); + _searchHashOrCashtag = IsHashOrCashtagSearchQuery(query); } return query; } @@ -2860,11 +2863,12 @@ bool Widget::applySearchState(SearchState state) { state.fromPeer = nullptr; } if (state.tab == ChatSearchTab::PublicPosts - && !IsHashtagSearchQuery(state.query)) { + && IsHashOrCashtagSearchQuery(state.query) == HashOrCashtag::None) { state.tab = (_openedForum && !state.inChat) ? ChatSearchTab::ThisPeer : ChatSearchTab::MyMessages; - } else if (!state.inChat && !_searchingHashtag) { + } else if (!state.inChat + && _searchHashOrCashtag == HashOrCashtag::None) { state.tab = (forum || _openedForum) ? ChatSearchTab::ThisPeer : ChatSearchTab::MyMessages; @@ -2904,7 +2908,7 @@ bool Widget::applySearchState(SearchState state) { && !state.inChat && !_openedForum) || (state.tab == ChatSearchTab::PublicPosts - && !_searchingHashtag)) { + && _searchHashOrCashtag == HashOrCashtag::None)) { state.tab = state.inChat.topic() ? ChatSearchTab::ThisTopic : (state.inChat.owningHistory() || state.inChat.sublist()) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 46e5b55e0..37fdcaae2 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -79,6 +79,7 @@ enum class SearchRequestDelay : uchar; class Suggestions; class ChatSearchIn; enum class ChatSearchTab : uchar; +enum class HashOrCashtag : uchar; class Widget final : public Window::AbstractSectionWidget { public: @@ -314,7 +315,7 @@ private: object_ptr _scrollToTop; bool _scrollToTopIsShown = false; bool _forumSearchRequested = false; - bool _searchingHashtag = false; + HashOrCashtag _searchHashOrCashtag = {}; Data::Folder *_openedFolder = nullptr; Data::Forum *_openedForum = nullptr; diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp index 0524b3a63..310f687ba 100644 --- a/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp @@ -199,12 +199,14 @@ void Action::handleKeyPress(not_null e) { FixedHashtagSearchQuery FixHashtagSearchQuery( const QString &query, - int cursorPosition) { + int cursorPosition, + HashOrCashtag tag) { const auto trimmed = query.trimmed(); const auto hash = int(trimmed.isEmpty() ? query.size() : query.indexOf(trimmed)); const auto start = std::min(cursorPosition, hash); + const auto first = QChar(tag == HashOrCashtag::Cashtag ? '$' : '#'); auto result = query.mid(0, start); for (const auto &ch : query.mid(start)) { if (ch.isSpace()) { @@ -213,33 +215,41 @@ FixedHashtagSearchQuery FixHashtagSearchQuery( } continue; } else if (result.size() == start) { - result += '#'; - if (ch != '#') { + result += first; + if (ch != first) { ++cursorPosition; } } - if (ch != '#') { + if (ch != first) { result += ch; } } if (result.size() == start) { - result += '#'; + result += first; ++cursorPosition; } return { result, cursorPosition }; } -bool IsHashtagSearchQuery(const QString &query) { +HashOrCashtag IsHashOrCashtagSearchQuery(const QString &query) { const auto trimmed = query.trimmed(); - if (trimmed.isEmpty() || trimmed[0] != '#') { - return false; - } - for (const auto &ch : trimmed) { - if (ch.isSpace()) { - return false; + const auto first = trimmed.isEmpty() ? QChar() : trimmed[0]; + if (first == '#') { + for (const auto &ch : trimmed) { + if (ch.isSpace()) { + return HashOrCashtag::None; + } } + return HashOrCashtag::Hashtag; + } else if (first == '$') { + for (const auto &ch : trimmed.midRef(1)) { + if (ch < 'A' || ch > 'Z') { + return HashOrCashtag::None; + } + } + return HashOrCashtag::Cashtag; } - return true; + return HashOrCashtag::None; } void ChatSearchIn::Section::update() { diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_in.h b/Telegram/SourceFiles/dialogs/ui/chat_search_in.h index 5e99f2d05..b5b742616 100644 --- a/Telegram/SourceFiles/dialogs/ui/chat_search_in.h +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_in.h @@ -87,14 +87,21 @@ private: }; +enum class HashOrCashtag : uchar { + None, + Hashtag, + Cashtag, +}; + struct FixedHashtagSearchQuery { QString text; int cursorPosition = 0; }; [[nodiscard]] FixedHashtagSearchQuery FixHashtagSearchQuery( const QString &query, - int cursorPosition); + int cursorPosition, + HashOrCashtag tag); -[[nodiscard]] bool IsHashtagSearchQuery(const QString &query); +[[nodiscard]] HashOrCashtag IsHashOrCashtagSearchQuery(const QString &query); } // namespace Dialogs From 77d214d2a501cfd5e76b5b5a42f48e61ddf71dba Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 28 Jun 2024 10:14:23 +0400 Subject: [PATCH 122/142] Support paid_media_allowed flag in ChannelFull. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/boxes/send_files_box.cpp | 8 ++++++-- Telegram/SourceFiles/data/data_channel.cpp | 8 +++++++- Telegram/SourceFiles/data/data_channel.h | 2 ++ Telegram/SourceFiles/history/history_item.cpp | 7 +++++++ Telegram/SourceFiles/mtproto/scheme/api.tl | 7 +++++-- 6 files changed, 28 insertions(+), 5 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 01a1e7cf5..622bd3e5b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4175,6 +4175,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_restricted_send_polls_all" = "Posting polls isn't allowed in this group."; "lng_restricted_send_public_polls" = "Sorry, public polls can't be forwarded to channels."; +"lng_restricted_send_paid_media" = "Sorry, paid media can't be sent to this channel."; "lng_restricted_send_voice_messages" = "{user} restricted sending of voice messages to them."; "lng_restricted_send_video_messages" = "{user} restricted sending of video messages to them."; diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index ea74b4d00..01ecb2b4d 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/painter.h" #include "ui/vertical_list.h" #include "lottie/lottie_single_player.h" +#include "data/data_channel.h" #include "data/data_document.h" #include "data/data_user.h" #include "data/data_peer_values.h" // Data::AmPremiumValue. @@ -706,8 +707,11 @@ bool SendFilesBox::hasSpoilerMenu() const { bool SendFilesBox::canChangePrice() const { const auto way = _sendWay.current(); - return _captionToPeer - && _captionToPeer->isBroadcast() + const auto broadcast = _captionToPeer + ? _captionToPeer->asBroadcast() + : nullptr; + return broadcast + && broadcast->canPostPaidMedia() && _list.canChangePrice( way.groupFiles() && way.sendImagesAsPhotos(), way.sendImagesAsPhotos()); diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 2ba8346d2..2f7694395 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -583,6 +583,10 @@ bool ChannelData::canDeleteStories() const { || (adminRights() & AdminRight::DeleteStories); } +bool ChannelData::canPostPaidMedia() const { + return canPostMessages() && (flags() & Flag::PaidMediaAllowed); +} + bool ChannelData::anyoneCanAddMembers() const { return !(defaultRestrictions() & Restriction::AddParticipants); } @@ -1084,7 +1088,8 @@ void ApplyChannelUpdate( | Flag::ParticipantsHidden | Flag::CanGetStatistics | Flag::ViewAsMessages - | Flag::CanViewRevenue; + | Flag::CanViewRevenue + | Flag::PaidMediaAllowed; channel->setFlags((channel->flags() & ~mask) | (update.is_can_set_username() ? Flag::CanSetUsername : Flag()) | (update.is_can_view_participants() @@ -1101,6 +1106,7 @@ void ApplyChannelUpdate( | (update.is_view_forum_as_messages() ? Flag::ViewAsMessages : Flag()) + | (update.is_paid_media_allowed() ? Flag::PaidMediaAllowed : Flag()) | (update.is_can_view_revenue() ? Flag::CanViewRevenue : Flag())); channel->setUserpicPhoto(update.vchat_photo()); if (const auto migratedFrom = update.vmigrated_from_chat_id()) { diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 6dbe84329..d6f880bda 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -66,6 +66,7 @@ enum class ChannelDataFlag : uint64 { ViewAsMessages = (1ULL << 30), SimilarExpanded = (1ULL << 31), CanViewRevenue = (1ULL << 32), + PaidMediaAllowed = (1ULL << 33), }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; using ChannelDataFlags = base::flags; @@ -357,6 +358,7 @@ public: [[nodiscard]] bool canPostStories() const; [[nodiscard]] bool canEditStories() const; [[nodiscard]] bool canDeleteStories() const; + [[nodiscard]] bool canPostPaidMedia() const; [[nodiscard]] bool hiddenPreHistory() const; [[nodiscard]] bool canViewMembers() const; [[nodiscard]] bool canViewAdmins() const; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 7203aceb1..d9c306013 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -2389,6 +2389,13 @@ std::optional HistoryItem::errorTextForForward( && _media->poll()->publicVotes() && peer->isBroadcast()) { return tr::lng_restricted_send_public_polls(tr::now); + } else if (_media + && _media->invoice() + && _media->invoice()->isPaidMedia + && peer->isBroadcast() + && peer->isFullLoaded() + && !peer->asBroadcast()->canPostPaidMedia()) { + return tr::lng_restricted_send_paid_media(tr::now); } else if (!Data::CanSend(to, requiredRight, false)) { return tr::lng_forward_cant(tr::now); } diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index f5e1ba2b3..91b310021 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -102,7 +102,7 @@ channel#aadfc8f flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5 channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#2633421b flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions reactions_limit:flags.20?int = ChatFull; -channelFull#bbab348d flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet = ChatFull; +channelFull#bbab348d flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -763,7 +763,7 @@ auth.sentCodeTypeMissedCall#82006484 prefix:string length:int = auth.SentCodeTyp auth.sentCodeTypeEmailCode#f450f59b flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true email_pattern:string length:int reset_available_period:flags.3?int reset_pending_date:flags.4?int = auth.SentCodeType; auth.sentCodeTypeSetUpEmailRequired#a5491dea flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true = auth.SentCodeType; auth.sentCodeTypeFragmentSms#d9565c39 url:string length:int = auth.SentCodeType; -auth.sentCodeTypeFirebaseSms#13c90f17 flags:# nonce:flags.0?bytes play_integrity_nonce:flags.2?bytes receipt:flags.1?string push_timeout:flags.1?int length:int = auth.SentCodeType; +auth.sentCodeTypeFirebaseSms#9fd736 flags:# nonce:flags.0?bytes play_integrity_project_id:flags.2?long play_integrity_nonce:flags.2?bytes receipt:flags.1?string push_timeout:flags.1?int length:int = auth.SentCodeType; auth.sentCodeTypeSmsWord#a416ac81 flags:# beginning:flags.0?string = auth.SentCodeType; auth.sentCodeTypeSmsPhrase#b37794af flags:# beginning:flags.0?string = auth.SentCodeType; @@ -1827,6 +1827,8 @@ payments.starsRevenueWithdrawalUrl#1dab80b7 url:string = payments.StarsRevenueWi payments.starsRevenueAdsAccountUrl#394e7f21 url:string = payments.StarsRevenueAdsAccountUrl; +inputStarsTransaction#206ae6d1 flags:# refund:flags.0?true id:string = InputStarsTransaction; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -2375,6 +2377,7 @@ 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; payments.getStarsRevenueAdsAccountUrl#d1d7efc5 peer:InputPeer = payments.StarsRevenueAdsAccountUrl; +payments.getStarsTransactionsByID#27842d2e peer:InputPeer id:Vector = payments.StarsStatus; 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; From 66f375d2c68bac36dfa037bf2fd02d3db7e6bd9c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 28 Jun 2024 13:22:28 +0300 Subject: [PATCH 123/142] Fixed endless memory copying in list of credits history entries. --- Telegram/SourceFiles/ui/effects/credits_graphics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index e8290d56b..9f48191f0 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -243,7 +243,7 @@ PaintRoundImageCallback GenerateCreditsPaintEntryCallback( }; const auto state = std::make_shared(); state->view = photo->createMediaView(); - photo->load(Data::PhotoSize::Thumbnail, {}); + photo->load(Data::PhotoSize::Large, {}); rpl::single(rpl::empty_value()) | rpl::then( photo->owner().session().downloaderTaskFinished() From 29b7673b882f7f9a40bae06b4026a10252baa51a Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 28 Jun 2024 13:23:07 +0300 Subject: [PATCH 124/142] Simplified handle of incoming credit history entries. --- Telegram/SourceFiles/api/api_credits.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 30f47db63..fb0a4e9c6 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -111,10 +111,7 @@ constexpr auto kTransactionsLimit = 100; ? base::unixtime::parse(tl.data().vtransaction_date()->v) : QDateTime(), .successLink = qs(tl.data().vtransaction_url().value_or_empty()), - .in = (!isBot || tl.data().is_refund()) - && !tl.data().is_pending() - && !tl.data().is_failed() - && !tl.data().vextended_media(), + .in = (int64(tl.data().vstars().v) >= 0), }; } From 4f7b5ca7da86caec207fdab08c02e1e4dd529055 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 28 Jun 2024 13:24:03 +0300 Subject: [PATCH 125/142] Added process of server notification for disabled withdrawal feature. --- Telegram/SourceFiles/api/api_updates.cpp | 6 ++++++ Telegram/SourceFiles/api/api_updates.h | 3 +++ .../earn/info_earn_inner_widget.cpp | 20 ++++++++++++++++--- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 2d7207bae..1951dc292 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -2121,6 +2121,8 @@ void Updates::feedUpdate(const MTPUpdate &update) { }; if (IsForceLogoutNotification(d)) { Core::App().forceLogOut(&session().account(), text); + } else if (IsWithdrawalNotification(d)) { + return; } else if (d.is_popup()) { const auto &windows = session().windows(); if (!windows.empty()) { @@ -2622,4 +2624,8 @@ void Updates::feedUpdate(const MTPUpdate &update) { } } +bool IsWithdrawalNotification(const MTPDupdateServiceNotification &data) { + return qs(data.vtype()).startsWith(u"API_WITHDRAWAL_FEATURE_DISABLED_"_q); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_updates.h b/Telegram/SourceFiles/api/api_updates.h index f21baa964..c028ae75a 100644 --- a/Telegram/SourceFiles/api/api_updates.h +++ b/Telegram/SourceFiles/api/api_updates.h @@ -211,4 +211,7 @@ private: }; +[[nodiscard]] bool IsWithdrawalNotification( + const MTPDupdateServiceNotification &); + } // namespace Api diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp index e58149319..0623a7bce 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp @@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_earn.h" #include "api/api_filter_updates.h" #include "api/api_statistics.h" +#include "api/api_text_entities.h" +#include "api/api_updates.h" #include "base/unixtime.h" #include "boxes/peers/edit_peer_color_box.h" // AddLevelBadge. #include "chat_helpers/stickers_emoji_pack.h" @@ -36,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "statistics/chart_widget.h" #include "ui/basic_click_handlers.h" #include "ui/boxes/boost_box.h" +#include "ui/boxes/confirm_box.h" #include "ui/controls/userpic_button.h" #include "ui/effects/animation_value_f.h" #include "ui/effects/credits_graphics.h" @@ -288,9 +291,8 @@ void InnerWidget::load() { _loaded.events_starting_with(false) | rpl::map(!rpl::mappers::_1), _showFinished.events()); - const auto fail = [show = _controller->uiShow()](const QString &error) { - show->showToast(error); - }; + const auto show = _controller->uiShow(); + const auto fail = [=](const QString &error) { show->showToast(error); }; const auto finish = [=] { _loaded.fire(true); @@ -303,6 +305,7 @@ void InnerWidget::load() { const MTPUpdates &updates) { using TLCreditsUpdate = MTPDupdateStarsRevenueStatus; using TLCurrencyUpdate = MTPDupdateBroadcastRevenueTransactions; + using TLNotificationUpdate = MTPDupdateServiceNotification; Api::PerformForUpdate(updates, [&]( const TLCreditsUpdate &d) { if (peerId == peerFromMTP(d.vpeer())) { @@ -325,6 +328,17 @@ void InnerWidget::load() { _stateUpdated.fire({}); } }); + Api::PerformForUpdate(updates, [&]( + const TLNotificationUpdate &d) { + if (Api::IsWithdrawalNotification(d) && d.is_popup()) { + show->show(Ui::MakeInformBox(TextWithEntities{ + qs(d.vmessage()), + Api::EntitiesFromMTP(& + _peer->session(), + d.ventities().v), + })); + } + }); }, lifetime()); }; From a95fb5b28d7376eb183721e58202ba5b1249a997 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 28 Jun 2024 13:32:34 +0300 Subject: [PATCH 126/142] Slightly improved withdraw phrases to fit small buttons. --- Telegram/Resources/langs/lang.strings | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 622bd3e5b..95559a1c7 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -5238,10 +5238,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_earn_balance_title" = "Available balance"; "lng_bot_earn_balance_about" = "Stars from your total balance become available for spending on ads and rewards 21 days after they are earned."; "lng_bot_earn_balance_about_url" = "https://telegram.org/tos/stars"; -"lng_bot_earn_balance_button#one" = "Withdraw {emoji} {count} via Fragment"; -"lng_bot_earn_balance_button#other" = "Withdraw {emoji} {count} via Fragment"; -"lng_bot_earn_balance_button_all" = "Withdraw all stars via Fragment"; -"lng_bot_earn_balance_button_locked" = "Withdraw via Fragment"; +"lng_bot_earn_balance_button#one" = "Withdraw {emoji} {count}"; +"lng_bot_earn_balance_button#other" = "Withdraw {emoji} {count}"; +"lng_bot_earn_balance_button_all" = "Withdraw all stars"; +"lng_bot_earn_balance_button_locked" = "Withdraw"; "lng_bot_earn_balance_button_buy_ads" = "Buy Ads"; "lng_bot_earn_learn_credits_out_about" = "You can withdraw Stars using Fragment, or use Stars to advertise your bot. {link}"; "lng_bot_earn_out_ph" = "Enter amount to withdraw"; From 1ed81b1c9c420b7b848b0fc5c58931710f065c53 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 28 Jun 2024 22:04:15 +0300 Subject: [PATCH 127/142] Improved process of mtp updates for revenue credits in earn section. --- .../earn/info_earn_inner_widget.cpp | 46 +++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp index 0623a7bce..82e020d68 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp @@ -308,14 +308,25 @@ void InnerWidget::load() { using TLNotificationUpdate = MTPDupdateServiceNotification; Api::PerformForUpdate(updates, [&]( const TLCreditsUpdate &d) { - if (peerId == peerFromMTP(d.vpeer())) { - apiCredits->request( - ) | rpl::start_with_error_done(fail, [=] { - state->apiCreditsLifetime.destroy(); - _state.creditsEarn = state->apiCredits.data(); - _stateUpdated.fire({}); - }, state->apiCreditsLifetime); + if (peerId != peerFromMTP(d.vpeer())) { + return; } + const auto &data = d.vstatus().data(); + auto &e = _state.creditsEarn; + e.currentBalance = data.vcurrent_balance().v; + e.availableBalance = data.vavailable_balance().v; + e.overallRevenue = data.voverall_revenue().v; + e.overallRevenue = data.voverall_revenue().v; + e.isWithdrawalEnabled = data.is_withdrawal_enabled(); + e.nextWithdrawalAt = data.vnext_withdrawal_at() + ? base::unixtime::parse( + data.vnext_withdrawal_at()->v) + : QDateTime(); + state->apiCreditsHistory.request({}, [=]( + const Data::CreditsStatusSlice &data) { + _state.creditsStatusSlice = data; + _stateUpdated.fire({}); + }); }); Api::PerformForUpdate(updates, [&]( const TLCurrencyUpdate &d) { @@ -710,7 +721,8 @@ void InnerWidget::fill() { const auto addOverview = [&]( rpl::producer currencyValue, rpl::producer creditsValue, - const tr::phrase<> &text) { + const tr::phrase<> &text, + bool showCredits) { const auto line = container->add( Ui::CreateSkipWidget(container, 0), st::boxRowPadding); @@ -764,7 +776,7 @@ void InnerWidget::fill() { size.width(), st::channelEarnOverviewMinorLabelSkip); secondMinorLabel->resizeToWidth( - (credits ? (available / 2) : available) + (showCredits ? (available / 2) : available) - size.width() - minorLabel->width()); secondMinorLabel->moveToLeft( @@ -780,7 +792,7 @@ void InnerWidget::fill() { st::channelEarnOverviewSubMinorLabelPos.y()); creditsSecondLabel->resizeToWidth( available - creditsSecondLabel->pos().x()); - if (!credits) { + if (!showCredits) { const auto x = std::numeric_limits::max(); icon->moveToLeft(x, 0); creditsLabel->moveToLeft(x, 0); @@ -801,22 +813,28 @@ void InnerWidget::fill() { auto availValueMap = [](const auto &v) { return v.availableBalance; }; auto currentValueMap = [](const auto &v) { return v.currentBalance; }; auto overallValueMap = [](const auto &v) { return v.overallRevenue; }; + const auto hasAnyCredits = creditsData.availableBalance + || creditsData.currentBalance + || creditsData.overallRevenue; addOverview( rpl::duplicate(currencyStateValue) | rpl::map(availValueMap), rpl::duplicate(creditsStateValue) | rpl::map(availValueMap), - tr::lng_channel_earn_available); + tr::lng_channel_earn_available, + hasAnyCredits); Ui::AddSkip(container); Ui::AddSkip(container); addOverview( rpl::duplicate(currencyStateValue) | rpl::map(currentValueMap), rpl::duplicate(creditsStateValue) | rpl::map(currentValueMap), - tr::lng_channel_earn_reward); + tr::lng_channel_earn_reward, + hasAnyCredits); Ui::AddSkip(container); Ui::AddSkip(container); addOverview( rpl::duplicate(currencyStateValue) | rpl::map(overallValueMap), rpl::duplicate(creditsStateValue) | rpl::map(overallValueMap), - tr::lng_channel_earn_total); + tr::lng_channel_earn_total, + hasAnyCredits); Ui::AddSkip(container); } #ifndef _DEBUG @@ -1408,6 +1426,8 @@ void InnerWidget::fill() { Ui::AddDivider(listsContainer); Ui::AddSkip(listsContainer); } + + listsContainer->resizeToWidth(width()); }; const auto historyContainer = container->add( From 68cb568898ac484444fc72a205e023d40be4fa08 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 28 Jun 2024 22:12:14 +0300 Subject: [PATCH 128/142] Fixed build with Qt6. --- Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp index 310f687ba..5fcc8f78b 100644 --- a/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp @@ -242,8 +242,8 @@ HashOrCashtag IsHashOrCashtagSearchQuery(const QString &query) { } return HashOrCashtag::Hashtag; } else if (first == '$') { - for (const auto &ch : trimmed.midRef(1)) { - if (ch < 'A' || ch > 'Z') { + for (auto it = trimmed.begin() + 1; it != trimmed.end(); ++it) { + if ((*it) < 'A' || (*it) > 'Z') { return HashOrCashtag::None; } } From deb9aa435b18107bab7275628783ba9b270e6e5e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 30 Jun 2024 15:13:54 +0300 Subject: [PATCH 129/142] Improved first display of personal channel in user profiles. --- .../info/profile/info_profile_actions.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index f7e37ced5..6e17ed549 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -1240,9 +1240,6 @@ object_ptr DetailsFiller::setupPersonalChannel( ) | rpl::map(rpl::mappers::_1 != nullptr)); result->finishAnimating(); - auto channelToggleValue = PersonalChannelValue( - user - ) | rpl::map([=] { return !!user->personalChannelId(); }); auto channel = PersonalChannelValue( user ) | rpl::start_spawning(result->lifetime()); @@ -1270,8 +1267,10 @@ object_ptr DetailsFiller::setupPersonalChannel( object_ptr>( container, object_ptr(container))); - onlyChannelWrap->toggleOn(rpl::duplicate(channelToggleValue) - | rpl::map(!rpl::mappers::_1)); + onlyChannelWrap->toggleOn(PersonalChannelValue(user) | rpl::map([=] { + return user->personalChannelId() + && !user->personalChannelMessageId(); + })); onlyChannelWrap->finishAnimating(); Ui::AddDivider(onlyChannelWrap->entity()); @@ -1312,7 +1311,12 @@ object_ptr DetailsFiller::setupPersonalChannel( object_ptr>( container, object_ptr(container))); - messageChannelWrap->toggleOn(rpl::duplicate(channelToggleValue)); + messageChannelWrap->toggleOn(PersonalChannelValue( + user + ) | rpl::map([=] { + return user->personalChannelId() + && user->personalChannelMessageId(); + })); messageChannelWrap->finishAnimating(); const auto clear = [=] { From 6dc3bd65e8e82220852cbf471d271784f9cfe58f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 30 Jun 2024 19:43:25 +0300 Subject: [PATCH 130/142] Improved spell checking on macOS. --- Telegram/lib_spellcheck | 2 +- cmake | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/lib_spellcheck b/Telegram/lib_spellcheck index 9b52030bf..59d922617 160000 --- a/Telegram/lib_spellcheck +++ b/Telegram/lib_spellcheck @@ -1 +1 @@ -Subproject commit 9b52030bfcd7e90e3e550231a3783ad1982fda78 +Subproject commit 59d92261783ae403e8708f972c36be3d21ab064d diff --git a/cmake b/cmake index 104406258..5742caae6 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 104406258fa89f007aad69348cef9b433f128fc2 +Subproject commit 5742caae65e4163e7faec238eb4e3e5c219ad09c From 69d21f73ef387da9ffb022acaea593aeeee729a3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 28 Jun 2024 21:44:06 +0400 Subject: [PATCH 131/142] Separate window for forums/topics/sublists/archive. --- Telegram/CMakeLists.txt | 2 + .../SourceFiles/boxes/choose_filter_box.cpp | 2 +- Telegram/SourceFiles/calls/calls_call.cpp | 9 +- .../chat_helpers/compose/compose_show.cpp | 8 +- Telegram/SourceFiles/core/application.cpp | 237 ++++++++---------- Telegram/SourceFiles/core/application.h | 38 ++- .../data/data_download_manager.cpp | 2 +- .../dialogs/dialogs_inner_widget.cpp | 16 +- .../dialogs/dialogs_inner_widget.h | 2 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 66 +++-- Telegram/SourceFiles/dialogs/dialogs_widget.h | 1 + .../dialogs/ui/dialogs_suggestions.cpp | 1 + .../history/history_item_helpers.cpp | 4 +- .../SourceFiles/history/history_widget.cpp | 9 +- .../view/history_view_top_bar_widget.cpp | 47 +++- .../view/history_view_top_bar_widget.h | 1 + Telegram/SourceFiles/main/main_domain.cpp | 16 +- Telegram/SourceFiles/main/main_session.cpp | 3 +- Telegram/SourceFiles/mainwidget.cpp | 68 ++--- Telegram/SourceFiles/mainwidget.h | 12 +- Telegram/SourceFiles/mainwindow.cpp | 12 +- .../media/view/media_view_overlay_widget.cpp | 2 +- .../settings/settings_information.cpp | 6 +- Telegram/SourceFiles/window/main_window.cpp | 18 +- Telegram/SourceFiles/window/main_window.h | 5 +- .../window/notifications_manager.cpp | 2 +- .../SourceFiles/window/window_controller.cpp | 76 +++--- .../SourceFiles/window/window_controller.h | 26 +- .../SourceFiles/window/window_main_menu.cpp | 49 ++-- .../SourceFiles/window/window_peer_menu.cpp | 109 +++++--- .../SourceFiles/window/window_separate_id.cpp | 77 ++++++ .../SourceFiles/window/window_separate_id.h | 69 +++++ .../window/window_session_controller.cpp | 155 +++++++++--- .../window/window_session_controller.h | 17 +- 34 files changed, 753 insertions(+), 414 deletions(-) create mode 100644 Telegram/SourceFiles/window/window_separate_id.cpp create mode 100644 Telegram/SourceFiles/window/window_separate_id.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 2b41b09fc..e3051087e 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1546,6 +1546,8 @@ PRIVATE window/window_peer_menu.cpp window/window_peer_menu.h window/window_section_common.h + window/window_separate_id.cpp + window/window_separate_id.h window/window_session_controller.cpp window/window_session_controller.h window/window_session_controller_link_info.h diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp index bc61e9fe2..5af8577bf 100644 --- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/choose_filter_box.cpp @@ -81,7 +81,7 @@ void ChangeFilterById( MTP_int(filter.id()), filter.tl() )).done([=, chat = history->peer->name(), name = filter.title()] { - const auto account = &history->session().account(); + const auto account = not_null(&history->session().account()); if (const auto controller = Core::App().windowFor(account)) { controller->showToast((add ? tr::lng_filters_toast_add diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 527839363..9f1272859 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -706,7 +706,8 @@ bool Call::handleUpdate(const MTPPhoneCall &call) { } } if (data.is_need_rating() && _id && _accessHash) { - const auto window = Core::App().windowFor(_user); + const auto window = Core::App().windowFor( + Window::SeparateId(_user)); const auto session = &_user->session(); const auto callId = _id; const auto callAccessHash = _accessHash; @@ -1402,7 +1403,8 @@ void Call::handleRequestError(const QString &error) { _user->name()) : QString(); if (!inform.isEmpty()) { - if (const auto window = Core::App().windowFor(_user)) { + if (const auto window = Core::App().windowFor( + Window::SeparateId(_user))) { window->show(Ui::MakeInformBox(inform)); } else { Ui::show(Ui::MakeInformBox(inform)); @@ -1420,7 +1422,8 @@ void Call::handleControllerError(const QString &error) { ? tr::lng_call_error_audio_io(tr::now) : QString(); if (!inform.isEmpty()) { - if (const auto window = Core::App().windowFor(_user)) { + if (const auto window = Core::App().windowFor( + Window::SeparateId(_user))) { window->show(Ui::MakeInformBox(inform)); } else { Ui::show(Ui::MakeInformBox(inform)); diff --git a/Telegram/SourceFiles/chat_helpers/compose/compose_show.cpp b/Telegram/SourceFiles/chat_helpers/compose/compose_show.cpp index 01bc33b8c..ba904b77c 100644 --- a/Telegram/SourceFiles/chat_helpers/compose/compose_show.cpp +++ b/Telegram/SourceFiles/chat_helpers/compose/compose_show.cpp @@ -30,15 +30,15 @@ ResolveWindow ResolveWindowDefault() { return (Window::SessionController*)nullptr; }; auto &app = Core::App(); + const auto account = not_null(&session->account()); if (const auto a = check(app.activeWindow())) { return a; } else if (const auto b = check(app.activePrimaryWindow())) { return b; - } else if (const auto c = check(app.windowFor(&session->account()))) { + } else if (const auto c = check(app.windowFor(account))) { return c; - } else if (const auto d = check( - app.ensureSeparateWindowForAccount( - &session->account()))) { + } else if (const auto d = check(app.ensureSeparateWindowFor( + account))) { return d; } return nullptr; diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 8e16868c9..21d3aa7ca 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "data/data_abstract_structure.h" +#include "data/data_forum.h" #include "data/data_photo.h" #include "data/data_document.h" #include "data/data_session.h" @@ -91,6 +92,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "payments/payments_checkout_process.h" #include "export/export_manager.h" #include "webrtc/webrtc_environment.h" +#include "window/window_separate_id.h" #include "window/window_session_controller.h" #include "window/window_controller.h" #include "boxes/abstract_box.h" @@ -209,8 +211,7 @@ Application::~Application() { setLastActiveWindow(nullptr); _windowInSettings = _lastActivePrimaryWindow = nullptr; _closingAsyncWindows.clear(); - _secondaryWindows.clear(); - _primaryWindows.clear(); + _windows.clear(); _mediaView = nullptr; _notifications->clearAllFast(); @@ -315,8 +316,8 @@ void Application::run() { // Create mime database, so it won't be slow later. QMimeDatabase().mimeTypeForName(u"text/plain"_q); - _primaryWindows.emplace(nullptr, std::make_unique()); - setLastActiveWindow(_primaryWindows.front().second.get()); + _windows.emplace(nullptr, std::make_unique()); + setLastActiveWindow(_windows.front().second.get()); _windowInSettings = _lastActivePrimaryWindow = _lastActiveWindow; _domain->activeChanges( @@ -405,7 +406,7 @@ void Application::run() { } void Application::showAccount(not_null account) { - if (const auto separate = separateWindowForAccount(account)) { + if (const auto separate = separateWindowFor(account)) { _lastActivePrimaryWindow = separate; separate->activate(); } else if (const auto last = activePrimaryWindow()) { @@ -413,13 +414,13 @@ void Application::showAccount(not_null account) { } } -void Application::checkWindowAccount(not_null window) { - const auto account = window->maybeAccount(); - for (auto &[key, existing] : _primaryWindows) { - if (existing.get() == window && key != account) { +void Application::checkWindowId(not_null window) { + const auto id = window->id(); + for (auto &[existingId, existing] : _windows) { + if (existing.get() == window && existingId != id) { auto found = std::move(existing); - _primaryWindows.remove(key); - _primaryWindows.emplace(account, std::move(found)); + _windows.remove(existingId); + _windows.emplace(id, std::move(found)); break; } } @@ -495,10 +496,7 @@ void Application::startSystemDarkModeViewer() { void Application::enumerateWindows(Fn)> callback) const { - for (const auto &window : ranges::views::values(_primaryWindows)) { - callback(window.get()); - } - for (const auto &window : ranges::views::values(_secondaryWindows)) { + for (const auto &window : ranges::views::values(_windows)) { callback(window.get()); } } @@ -607,10 +605,7 @@ void Application::clearEmojiSourceImages() { } bool Application::isActiveForTrayMenu() const { - return ranges::any_of(ranges::views::values(_primaryWindows), [=]( - const std::unique_ptr &controller) { - return controller->widget()->isActiveForTrayMenu(); - }) || ranges::any_of(ranges::views::values(_secondaryWindows), [=]( + return ranges::any_of(ranges::views::values(_windows), [=]( const std::unique_ptr &controller) { return controller->widget()->isActiveForTrayMenu(); }); @@ -1287,44 +1282,36 @@ Window::Controller *Application::activePrimaryWindow() const { return _lastActivePrimaryWindow; } -Window::Controller *Application::separateWindowForAccount( - not_null account) const { - for (const auto &[openedAccount, window] : _primaryWindows) { - if (openedAccount == account.get()) { +Window::Controller *Application::separateWindowFor( + Window::SeparateId id) const { + for (const auto &[existingId, window] : _windows) { + if (existingId == id) { return window.get(); } } return nullptr; } -Window::Controller *Application::separateWindowForPeer( - not_null peer) const { - for (const auto &[history, window] : _secondaryWindows) { - if (history->peer == peer) { - return window.get(); - } - } - return nullptr; -} - -Window::Controller *Application::ensureSeparateWindowForPeer( - not_null peer, +Window::Controller *Application::ensureSeparateWindowFor( + Window::SeparateId id, MsgId showAtMsgId) { const auto activate = [&](not_null window) { window->activate(); return window; }; - - if (const auto existing = separateWindowForPeer(peer)) { - existing->sessionController()->showPeerHistory( - peer, - Window::SectionShow::Way::ClearStack, - showAtMsgId); + if (const auto existing = separateWindowFor(id)) { + if (id.thread && id.type == Window::SeparateType::Chat) { + existing->sessionController()->showThread( + id.thread, + showAtMsgId, + Window::SectionShow::Way::ClearStack); + } return activate(existing); } - const auto result = _secondaryWindows.emplace( - peer->owner().history(peer), - std::make_unique(peer, showAtMsgId) + + const auto result = _windows.emplace( + id, + std::make_unique(id, showAtMsgId) ).first->second.get(); processCreatedWindow(result); result->firstShow(); @@ -1332,55 +1319,63 @@ Window::Controller *Application::ensureSeparateWindowForPeer( return activate(result); } -Window::Controller *Application::ensureSeparateWindowForAccount( - not_null account) { - const auto activate = [&](not_null window) { - window->activate(); - return window; - }; - - if (const auto existing = separateWindowForAccount(account)) { - return activate(existing); - } - const auto result = _primaryWindows.emplace( - account, - std::make_unique(account) - ).first->second.get(); - processCreatedWindow(result); - result->firstShow(); - result->finishFirstShow(); - return activate(result); -} - -Window::Controller *Application::windowFor(not_null peer) const { - if (const auto separate = separateWindowForPeer(peer)) { - return separate; - } - return windowFor(&peer->account()); -} - -Window::Controller *Application::windowFor( - not_null account) const { - if (const auto separate = separateWindowForAccount(account)) { +Window::Controller *Application::windowFor(Window::SeparateId id) const { + if (const auto separate = separateWindowFor(id)) { return separate; + } else if (id && id.primary()) { + return windowFor(not_null(id.account)); } return activePrimaryWindow(); } +Window::Controller *Application::windowForShowingHistory( + not_null peer) const { + if (const auto separate = separateWindowFor(peer)) { + return separate; + } + auto result = (Window::Controller*)nullptr; + enumerateWindows([&](not_null window) { + if (const auto controller = window->sessionController()) { + const auto current = controller->activeChatCurrent(); + if (const auto history = current.history()) { + if (history->peer == peer) { + result = window; + } + } + } + }); + return result; +} + +Window::Controller *Application::windowForShowingForum( + not_null forum) const { + const auto id = Window::SeparateId( + Window::SeparateType::Forum, + forum->history()); + if (const auto separate = separateWindowFor(id)) { + return separate; + } + auto result = (Window::Controller*)nullptr; + enumerateWindows([&](not_null window) { + if (const auto controller = window->sessionController()) { + const auto current = controller->shownForum().current(); + if (forum == current) { + result = window; + } + } + }); + return result; +} + Window::Controller *Application::findWindow( not_null widget) const { const auto window = widget->window(); if (_lastActiveWindow && _lastActiveWindow->widget() == window) { return _lastActiveWindow; } - for (const auto &[account, primary] : _primaryWindows) { - if (primary->widget() == window) { - return primary.get(); - } - } - for (const auto &[history, secondary] : _secondaryWindows) { - if (secondary->widget() == window) { - return secondary.get(); + for (const auto &[id, controller] : _windows) { + if (controller->widget() == window) { + return controller.get(); } } return nullptr; @@ -1392,10 +1387,11 @@ Window::Controller *Application::activeWindow() const { bool Application::closeNonLastAsync(not_null window) { const auto hasOther = [&] { - for (const auto &[account, primary] : _primaryWindows) { - if (!_closingAsyncWindows.contains(primary.get()) - && primary.get() != window - && primary->maybeSession()) { + for (const auto &[id, controller] : _windows) { + if (id.primary() + && !_closingAsyncWindows.contains(controller.get()) + && controller.get() != window + && controller->maybeSession()) { return true; } } @@ -1457,10 +1453,10 @@ void Application::closeWindow(not_null window) { : nullptr; const auto next = nextFromStack ? nextFromStack - : (_primaryWindows.front().second.get() != window) - ? _primaryWindows.front().second.get() - : (_primaryWindows.back().second.get() != window) - ? _primaryWindows.back().second.get() + : (_windows.front().second.get() != window) + ? _windows.front().second.get() + : (_windows.back().second.get() != window) + ? _windows.back().second.get() : nullptr; Assert(next != window); @@ -1481,20 +1477,12 @@ void Application::closeWindow(not_null window) { } } _closingAsyncWindows.remove(window); - for (auto i = begin(_primaryWindows); i != end(_primaryWindows);) { + for (auto i = begin(_windows); i != end(_windows);) { if (i->second.get() == window) { Assert(_lastActiveWindow != window); Assert(_lastActivePrimaryWindow != window); Assert(_windowInSettings != window); - i = _primaryWindows.erase(i); - } else { - ++i; - } - } - for (auto i = begin(_secondaryWindows); i != end(_secondaryWindows);) { - if (i->second.get() == window) { - Assert(_lastActiveWindow != window); - i = _secondaryWindows.erase(i); + i = _windows.erase(i); } else { ++i; } @@ -1502,36 +1490,34 @@ void Application::closeWindow(not_null window) { const auto account = domain().started() ? &domain().active() : nullptr; - if (account && !_primaryWindows.contains(account) && _lastActiveWindow) { + if (account + && !_windows.contains(Window::SeparateId(account)) + && _lastActiveWindow) { domain().activate(&_lastActiveWindow->account()); } } void Application::closeChatFromWindows(not_null peer) { - if (const auto window = windowFor(peer) - ; window && !window->isPrimary()) { - closeWindow(window); - } - for (const auto &[history, window] : _secondaryWindows) { - if (const auto session = window->sessionController()) { - if (session->activeChatCurrent().peer() == peer) { - session->showPeerHistory( - window->singlePeer()->id, - Window::SectionShow::Way::ClearStack); - } - } - } - if (const auto window = windowFor(&peer->account())) { - if (const auto primary = window->sessionController()) { - if (primary->activeChatCurrent().peer() == peer) { - primary->clearSectionStack(); - } - if (const auto forum = primary->shownForum().current()) { - if (peer->forum() == forum) { - primary->closeForum(); + const auto closeOne = [&] { + for (const auto &[id, window] : _windows) { + if (id.thread && id.thread->peer() == peer) { + closeWindow(window.get()); + return true; + } else if (const auto controller = window->sessionController()) { + if (controller->activeChatCurrent().peer() == peer) { + controller->showByInitialId(); + } + if (const auto forum = controller->shownForum().current()) { + if (peer->forum() == forum) { + controller->closeForum(); + } } } } + return false; + }; + + while (closeOne()) { } } @@ -1737,11 +1723,8 @@ void Application::quitPreventFinished() { } void Application::quitDelayed() { - for (const auto &[account, window] : _primaryWindows) { - window->widget()->hide(); - } - for (const auto &[history, window] : _secondaryWindows) { - window->widget()->hide(); + for (const auto &[id, controller] : _windows) { + controller->widget()->hide(); } if (!_private->quitTimer.isActive()) { _private->quitTimer.setCallback([] { Sandbox::QuitWhenStarted(); }); diff --git a/Telegram/SourceFiles/core/application.h b/Telegram/SourceFiles/core/application.h index 08a29a35e..77fe09b7f 100644 --- a/Telegram/SourceFiles/core/application.h +++ b/Telegram/SourceFiles/core/application.h @@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/timer.h" #include "mtproto/mtproto_auth_key.h" #include "mtproto/mtproto_proxy_data.h" -#include "base/timer.h" +#include "window/window_separate_id.h" class History; @@ -29,11 +30,9 @@ namespace Window { class Controller; } // namespace Window -namespace Window { -namespace Notifications { +namespace Window::Notifications { class System; -} // namespace Notifications -} // namespace Window +} // namespace Window::Notifications namespace ChatHelpers { class EmojiKeywords; @@ -170,19 +169,17 @@ public: not_null widget) const; [[nodiscard]] Window::Controller *activeWindow() const; [[nodiscard]] Window::Controller *activePrimaryWindow() const; - [[nodiscard]] Window::Controller *separateWindowForAccount( - not_null account) const; - [[nodiscard]] Window::Controller *separateWindowForPeer( - not_null peer) const; - Window::Controller *ensureSeparateWindowForPeer( - not_null peer, - MsgId showAtMsgId); - Window::Controller *ensureSeparateWindowForAccount( - not_null account); + [[nodiscard]] Window::Controller *separateWindowFor( + Window::SeparateId id) const; + Window::Controller *ensureSeparateWindowFor( + Window::SeparateId id, + MsgId showAtMsgId = 0); [[nodiscard]] Window::Controller *windowFor( // Doesn't auto-switch. + Window::SeparateId id) const; + [[nodiscard]] Window::Controller *windowForShowingHistory( not_null peer) const; - [[nodiscard]] Window::Controller *windowFor( // Doesn't auto-switch. - not_null account) const; + [[nodiscard]] Window::Controller *windowForShowingForum( + not_null forum) const; [[nodiscard]] bool closeNonLastAsync( not_null window); void closeWindow(not_null window); @@ -195,7 +192,7 @@ public: void checkSystemDarkMode(); [[nodiscard]] bool isActiveForTrayMenu() const; void closeChatFromWindows(not_null peer); - void checkWindowAccount(not_null window); + void checkWindowId(not_null window); void activate(); // Media view interface. @@ -423,12 +420,9 @@ private: const std::unique_ptr _calls; const std::unique_ptr _iv; base::flat_map< - Main::Account*, - std::unique_ptr> _primaryWindows; + Window::SeparateId, + std::unique_ptr> _windows; base::flat_set> _closingAsyncWindows; - base::flat_map< - not_null, - std::unique_ptr> _secondaryWindows; std::vector> _windowStack; Window::Controller *_lastActiveWindow = nullptr; Window::Controller *_lastActivePrimaryWindow = nullptr; diff --git a/Telegram/SourceFiles/data/data_download_manager.cpp b/Telegram/SourceFiles/data/data_download_manager.cpp index 2fa5f05d4..29a1899e0 100644 --- a/Telegram/SourceFiles/data/data_download_manager.cpp +++ b/Telegram/SourceFiles/data/data_download_manager.cpp @@ -531,7 +531,7 @@ void DownloadManager::loadingStopWithConfirmation( return; } const auto window = Core::App().windowFor( - &item->history()->session().account()); + not_null(&item->history()->session().account())); if (!window) { return; } diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index fe5651774..1ac569e9c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -3676,7 +3676,7 @@ void InnerWidget::preloadRowsData() { } } -bool InnerWidget::chooseCollapsedRow() { +bool InnerWidget::chooseCollapsedRow(Qt::KeyboardModifiers modifiers) { if (_state != WidgetState::Default) { return false; } else if ((_collapsedSelected < 0) @@ -3769,7 +3769,15 @@ bool InnerWidget::chooseHashtag() { ChosenRow InnerWidget::computeChosenRow() const { if (_state == WidgetState::Default) { - if (_selected) { + if ((_collapsedSelected >= 0) + && (_collapsedSelected < _collapsedRows.size())) { + const auto &row = _collapsedRows[_collapsedSelected]; + Assert(row->folder != nullptr); + return { + .key = row->folder, + .message = Data::UnreadMessagePosition, + }; + } else if (_selected) { return { .key = _selected->key(), .message = Data::UnreadMessagePosition, @@ -3813,9 +3821,7 @@ bool InnerWidget::isUserpicPressOnWide() const { bool InnerWidget::chooseRow( Qt::KeyboardModifiers modifiers, MsgId pressedTopicRootId) { - if (chooseCollapsedRow()) { - return true; - } else if (chooseHashtag()) { + if (chooseHashtag()) { return true; } const auto modifyChosenRow = [&]( diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 033418af1..a0b80ef67 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -252,7 +252,7 @@ private: void repaintCollapsedFolderRow(not_null folder); void refreshWithCollapsedRows(bool toTop = false); bool needCollapsedRowsRefresh() const; - bool chooseCollapsedRow(); + bool chooseCollapsedRow(Qt::KeyboardModifiers modifiers); void switchToFilter(FilterId filterId); bool chooseHashtag(); ChosenRow computeChosenRow() const; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 913425a79..82c8f0384 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -562,6 +562,8 @@ void Widget::chosenRow(const ChosenRow &row) { if (topicJump) { if (controller()->shownForum().current() == topicJump->forum()) { controller()->closeForum(); + } else if (row.newWindow) { + controller()->showInNewWindow(Window::SeparateId(topicJump)); } else { if (!controller()->adaptive().isOneColumn()) { controller()->showForum( @@ -575,11 +577,17 @@ void Widget::chosenRow(const ChosenRow &row) { } return; } else if (const auto topic = row.key.topic()) { - session().data().saveViewAsMessages(topic->forum(), false); - controller()->showThread( - topic, - row.message.fullId.msg, - Window::SectionShow::Way::ClearStack); + if (row.newWindow) { + controller()->showInNewWindow( + Window::SeparateId(topic), + row.message.fullId.msg); + } else { + session().data().saveViewAsMessages(topic->forum(), false); + controller()->showThread( + topic, + row.message.fullId.msg, + Window::SectionShow::Way::ClearStack); + } } else if (history && row.userpicClick && (row.message.fullId.msg == ShowAtUnreadMsgId) @@ -595,16 +603,19 @@ void Widget::chosenRow(const ChosenRow &row) { const auto forum = history->peer->forum(); if (controller()->shownForum().current() == forum) { controller()->closeForum(); - return; - } - controller()->showForum( - forum, - Window::SectionShow().withChildColumn()); - if (forum->channel()->viewForumAsMessages()) { - controller()->showThread( - history, - ShowAtUnreadMsgId, - Window::SectionShow::Way::ClearStack); + } else if (row.newWindow) { + controller()->showInNewWindow( + Window::SeparateId(Window::SeparateType::Forum, history)); + } else { + controller()->showForum( + forum, + Window::SectionShow().withChildColumn()); + if (forum->channel()->viewForumAsMessages()) { + controller()->showThread( + history, + ShowAtUnreadMsgId, + Window::SectionShow::Way::ClearStack); + } } return; } else if (history) { @@ -630,6 +641,12 @@ void Widget::chosenRow(const ChosenRow &row) { return; } } + if (row.newWindow) { + controller()->showInNewWindow(Window::SeparateId( + Window::SeparateType::Archive, + &session())); + return; + } controller()->openFolder(folder); hideChildList(); } @@ -1847,13 +1864,21 @@ void Widget::slideFinished() { void Widget::escape() { if (!cancelSearch({ .jumpBackToSearchedChat = true })) { - if (controller()->shownForum().current()) { - controller()->closeForum(); + if (const auto forum = controller()->shownForum().current()) { + const auto id = controller()->windowId(); + const auto initial = id.forum(); + if (!initial) { + controller()->closeForum(); + } else if (initial != forum) { + controller()->showForum(initial); + } } else if (controller()->openedFolder().current()) { - controller()->closeFolder(); + if (!controller()->windowId().folder()) { + controller()->closeFolder(); + } } else if (controller()->activeChatEntryCurrent().key) { controller()->content()->dialogsCancelled(); - } else { + } else if (controller()->isPrimary()) { const auto filters = &session().data().chatsFilters(); const auto &list = filters->list(); const auto first = list.empty() ? FilterId() : list.front().id(); @@ -2704,6 +2729,9 @@ void Widget::updateForceDisplayWide() { void Widget::showForum( not_null forum, const Window::SectionShow ¶ms) { + if (_openedForum == forum) { + return; + } const auto nochat = !controller()->mainSectionShown(); if (!params.childColumn || (Core::App().settings().dialogsWidthRatio(nochat) == 0.) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 37fdcaae2..347683d0c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -57,6 +57,7 @@ namespace Window { class SessionController; class ConnectionState; struct SectionShow; +struct SeparateId; } // namespace Window namespace Dialogs::Stories { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index ad6fe1f69..a19200d8a 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/dynamic_thumbnails.h" #include "ui/painter.h" #include "ui/unread_badge_paint.h" +#include "window/window_separate_id.h" #include "window/window_session_controller.h" #include "window/window_peer_menu.h" #include "styles/style_chat.h" diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 9a6423bcc..30303e5f8 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -317,7 +317,7 @@ ClickHandlerPtr JumpToMessageClickHandler( TextWithEntities highlightPart, int highlightPartOffsetHint) { return std::make_shared([=] { - const auto separate = Core::App().separateWindowForPeer(peer); + const auto separate = Core::App().separateWindowFor(peer); const auto controller = separate ? separate->sessionController() : peer->session().tryResolveWindow(); @@ -347,7 +347,7 @@ ClickHandlerPtr JumpToStoryClickHandler( not_null peer, StoryId storyId) { return std::make_shared([=] { - const auto separate = Core::App().separateWindowForPeer(peer); + const auto separate = Core::App().separateWindowFor(peer); const auto controller = separate ? separate->sessionController() : peer->session().tryResolveWindow(); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index b19c0222f..164909c20 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -823,7 +823,7 @@ HistoryWidget::HistoryWidget( if (flags & PeerUpdateFlag::UnavailableReason) { const auto unavailable = _peer->computeUnavailableReason(); if (!unavailable.isEmpty()) { - const auto account = &_peer->account(); + const auto account = not_null(&_peer->account()); closeCurrent(); if (const auto primary = Core::App().windowFor(account)) { primary->showToast(unavailable); @@ -3342,7 +3342,8 @@ void HistoryWidget::messagesFailed(const MTP::Error &error, int requestId) { || error.type() == u"USER_BANNED_IN_CHANNEL"_q) { auto was = _peer; closeCurrent(); - if (const auto primary = Core::App().windowFor(&was->account())) { + const auto wasAccount = not_null(&was->account()); + if (const auto primary = Core::App().windowFor(wasAccount)) { primary->showToast((was && was->isMegagroup()) ? tr::lng_group_not_accessible(tr::now) : tr::lng_channel_not_accessible(tr::now)); @@ -4974,8 +4975,8 @@ bool HistoryWidget::updateCmdStartShown() { } bool HistoryWidget::searchInChatEmbedded(Dialogs::Key chat, QString query) { - const auto peer = chat.peer(); - if (!peer || peer != controller()->singlePeer()) { + const auto peer = chat.peer(); // windows todo + if (!peer || Window::SeparateId(peer) != controller()->windowId()) { return false; } else if (_peer != peer) { const auto weak = Ui::MakeWeak(this); diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index df5d902c2..92bd21800 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer_values.h" #include "data/data_group_call.h" // GroupCall::input. #include "data/data_folder.h" +#include "data/data_forum.h" #include "data/data_saved_sublist.h" #include "data/data_session.h" #include "data/data_stories.h" @@ -714,10 +715,13 @@ void TopBarWidget::mousePressEvent(QMouseEvent *e) { && !showSelectedState() && !_chooseForReportReason; if (handleClick) { + const auto archiveTop = (_activeChat.section == Section::ChatsList) + && _activeChat.key.folder(); if ((_animatingMode && _back->rect().contains(e->pos())) - || (_activeChat.section == Section::ChatsList - && _activeChat.key.folder())) { - backClicked(); + || archiveTop) { + if (!rootChatsListBar()) { + backClicked(); + } } else { infoClicked(); } @@ -890,9 +894,22 @@ void TopBarWidget::setCustomTitle(const QString &title) { } } +bool TopBarWidget::rootChatsListBar() const { + if (_activeChat.section != Section::ChatsList) { + return false; + } + const auto id = _controller->windowId(); + const auto separateFolder = id.folder(); + const auto separateForum = id.forum(); + const auto active = _activeChat.key; + return (separateForum && separateForum->history() == active.history()) + || (separateFolder && separateFolder == active.folder()); +} + void TopBarWidget::refreshInfoButton() { if (_activeChat.key.topic() - || _activeChat.section == Section::ChatsList) { + || (_activeChat.section == Section::ChatsList + && !rootChatsListBar())) { _info.destroy(); } else if (const auto peer = _activeChat.key.peer()) { auto info = object_ptr( @@ -989,6 +1006,14 @@ void TopBarWidget::updateControlsGeometry() { _leftTaken += _back->width(); } if (_info && !_info->isHidden()) { + if (_back->isHidden() && _narrowRatio > 0.) { + const auto &infoSt = st::topBarInfoButton; + const auto middle = (_narrowWidth - infoSt.photoSize) / 2; + _leftTaken = anim::interpolate( + _leftTaken, + middle - infoSt.photoPosition.x(), + _narrowRatio); + } _info->moveToLeft(_leftTaken, otherButtonsTop); _leftTaken += _info->width(); } else if (_activeChat.key.topic() @@ -997,7 +1022,9 @@ void TopBarWidget::updateControlsGeometry() { } if (_searchField) { - const auto fieldLeft = _leftTaken; + const auto fieldLeft = _back->isHidden() + ? st::topBarArrowPadding.right() + : _leftTaken; const auto fieldTop = searchFieldTop + (height() - _searchField->height()) / 2; const auto fieldRight = st::dialogsFilterSkip @@ -1075,9 +1102,10 @@ void TopBarWidget::updateControlsVisibility() { _sendNow->setVisible(_canSendNow); const auto isOneColumn = _controller->adaptive().isOneColumn(); - auto backVisible = isOneColumn - || !_controller->content()->stackIsEmpty() - || (_activeChat.section == Section::ChatsList); + const auto backVisible = !rootChatsListBar() + && (isOneColumn + || (_activeChat.section == Section::ChatsList) + || !_controller->content()->stackIsEmpty()); _back->setVisible(backVisible && !_chooseForReportReason); _cancelChoose->setVisible(_chooseForReportReason.has_value()); if (_info) { @@ -1085,7 +1113,8 @@ void TopBarWidget::updateControlsVisibility() { && (isOneColumn || !_primaryWindow)); } if (_unreadBadge) { - _unreadBadge->setVisible(!_chooseForReportReason); + _unreadBadge->setVisible(!_chooseForReportReason + && !rootChatsListBar()); } const auto topic = _activeChat.key.topic(); const auto section = _activeChat.section; diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h index 5c94220b1..6b81c26a0 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h @@ -131,6 +131,7 @@ protected: private: struct EmojiInteractionSeenAnimation; + [[nodiscard]] bool rootChatsListBar() const; void refreshInfoButton(); void refreshLang(); void updateSearchVisibility(); diff --git a/Telegram/SourceFiles/main/main_domain.cpp b/Telegram/SourceFiles/main/main_domain.cpp index aafbcd2ac..9b4b12067 100644 --- a/Telegram/SourceFiles/main/main_domain.cpp +++ b/Telegram/SourceFiles/main/main_domain.cpp @@ -318,8 +318,8 @@ not_null Domain::add(MTP::Environment environment) { void Domain::addActivated(MTP::Environment environment, bool newWindow) { const auto added = [&](not_null account) { if (newWindow) { - Core::App().ensureSeparateWindowForAccount(account); - } else if (const auto window = Core::App().separateWindowForAccount( + Core::App().ensureSeparateWindowFor(account); + } else if (const auto window = Core::App().separateWindowFor( account)) { window->activate(); } else { @@ -371,11 +371,11 @@ void Domain::watchSession(not_null account) { void Domain::closeAccountWindows(not_null account) { auto another = (Main::Account*)nullptr; for (auto i = _accounts.begin(); i != _accounts.end(); ++i) { - const auto other = i->account.get(); + const auto other = not_null(i->account.get()); if (other == account) { continue; - } else if (Core::App().separateWindowForAccount(other)) { - const auto that = Core::App().separateWindowForAccount(account); + } else if (Core::App().separateWindowFor(other)) { + const auto that = Core::App().separateWindowFor(account); if (that) { that->close(); } @@ -411,7 +411,7 @@ void Domain::removeRedundantAccounts() { const auto was = _accounts.size(); for (auto i = _accounts.begin(); i != _accounts.end();) { - if (Core::App().separateWindowForAccount(i->account.get()) + if (Core::App().separateWindowFor(not_null(i->account.get())) || i->account->sessionExists()) { ++i; continue; @@ -442,7 +442,7 @@ void Domain::checkForLastProductionConfig( } void Domain::maybeActivate(not_null account) { - if (Core::App().separateWindowForAccount(account)) { + if (Core::App().separateWindowFor(account)) { activate(account); } else { Core::App().preventOrInvoke(crl::guard(account, [=] { @@ -452,7 +452,7 @@ void Domain::maybeActivate(not_null account) { } void Domain::activate(not_null account) { - if (const auto window = Core::App().separateWindowForAccount(account)) { + if (const auto window = Core::App().separateWindowFor(account)) { window->activate(); } if (_active.current() == account.get()) { diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index 951ec735a..ea6111574 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -497,7 +497,8 @@ Window::SessionController *Session::tryResolveWindow( if (forPeer) { auto primary = (Window::SessionController*)nullptr; for (const auto &window : _windows) { - if (window->singlePeer() == forPeer) { + const auto thread = window->windowId().thread; + if (thread && thread->peer() == forPeer) { return window; } else if (window->isPrimary()) { primary = window; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 025b66cf8..45ad3ef62 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_connecting_widget.h" #include "window/window_top_bar_wrap.h" #include "window/notifications_manager.h" +#include "window/window_separate_id.h" #include "window/window_slide_animation.h" #include "window/window_history_hider.h" #include "window/window_controller.h" @@ -232,19 +233,19 @@ MainWidget::MainWidget( , _controller(controller) , _dialogsWidth(st::columnMinimalWidthLeft) , _thirdColumnWidth(st::columnMinimalWidthThird) -, _sideShadow(isPrimary() - ? base::make_unique_q(this) - : nullptr) -, _dialogs(isPrimary() +, _dialogs(windowId().hasChatsList() ? base::make_unique_q( this, _controller, Dialogs::Widget::Layout::Main) : nullptr) , _history(std::in_place, this, _controller) +, _sideShadow(_dialogs + ? base::make_unique_q(this) + : nullptr) , _playerPlaylist(this, _controller) , _changelogs(Core::Changelogs::Create(&controller->session())) { - if (isPrimary()) { + if (_dialogs) { setupConnectingWidget(); } @@ -732,7 +733,7 @@ void MainWidget::hideSingleUseKeyboard(FullMsgId replyToId) { void MainWidget::searchMessages(const QString &query, Dialogs::Key inChat) { auto tags = Data::SearchTagsFromQuery(query); - if (controller()->isPrimary()) { + if (_dialogs) { auto state = Dialogs::SearchState{ .inChat = ((tags.empty() || inChat.sublist()) ? inChat @@ -758,7 +759,7 @@ void MainWidget::searchMessages(const QString &query, Dialogs::Key inChat) { if ((!_mainSection || !_mainSection->searchInChatEmbedded(inChat, query)) && !_history->searchInChatEmbedded(inChat, query)) { - const auto account = &session().account(); + const auto account = not_null(&session().account()); if (const auto window = Core::App().windowFor(account)) { if (const auto controller = window->sessionController()) { controller->content()->searchMessages(query, inChat); @@ -1238,36 +1239,35 @@ bool MainWidget::showHistoryInDifferentWindow( PeerId peerId, const SectionShow ¶ms, MsgId showAtMsgId) { + if (!peerId) { + return false; + } const auto peer = session().data().peer(peerId); - const auto account = &session().account(); - auto primary = Core::App().separateWindowForAccount(account); - if (const auto separate = Core::App().separateWindowForPeer(peer)) { - if (separate == &_controller->window()) { - return false; + if (const auto separateChat = _controller->windowId().chat()) { + if (const auto history = separateChat->asHistory()) { + if (history->peer == peer) { + return false; + } } - separate->sessionController()->showPeerHistory( + } + const auto window = Core::App().windowForShowingHistory(peer); + if (window == &_controller->window()) { + return false; + } else if (window) { + window->sessionController()->showPeerHistory( peerId, params, showAtMsgId); - separate->activate(); + window->activate(); return true; - } else if (isPrimary()) { - if (primary && primary != &_controller->window()) { - primary->sessionController()->showPeerHistory( - peerId, - params, - showAtMsgId); - primary->activate(); - return true; - } + } else if (windowId().hasChatsList()) { return false; - } else if (!peerId) { - return true; - } else if (singlePeer()->id == peerId) { - return false; - } else if (!primary) { + } + const auto account = not_null(&session().account()); + auto primary = Core::App().separateWindowFor(account); + if (!primary) { Core::App().domain().activate(account); - primary = Core::App().separateWindowForAccount(account); + primary = Core::App().separateWindowFor(account); } if (primary && &primary->account() == account) { primary->sessionController()->showPeerHistory( @@ -1293,7 +1293,7 @@ void MainWidget::showHistory( } const auto unavailable = peer->computeUnavailableReason(); if (!unavailable.isEmpty()) { - Assert(isPrimary()); + Assert(isPrimary()); // windows todo if (params.activation != anim::activation::background) { _controller->show(Ui::MakeInformBox(unavailable)); _controller->window().activate(); @@ -1510,7 +1510,7 @@ void MainWidget::showMessage( void MainWidget::showForum( not_null forum, const SectionShow ¶ms) { - Expects(isPrimary() || (singlePeer() && singlePeer()->forum() == forum)); + Expects(_dialogs != nullptr); _dialogs->showForum(forum, params); @@ -1846,8 +1846,8 @@ void MainWidget::checkMainSectionToLayer() { updateMainSectionShown(); } -PeerData *MainWidget::singlePeer() const { - return _controller->singlePeer(); +Window::SeparateId MainWidget::windowId() const { + return _controller->windowId(); } bool MainWidget::isPrimary() const { @@ -1957,7 +1957,7 @@ void MainWidget::showBackFromStack( } if (_stack.empty()) { - if (isPrimary()) { + if (_dialogs) { _controller->clearSectionStack(params); } crl::on_main(this, [=] { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 3653070ac..b97c7f98f 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -90,6 +90,7 @@ struct SectionSlideParams; struct SectionShow; enum class Column; class HistoryHider; +struct SeparateId; } // namespace Window namespace Calls { @@ -121,7 +122,7 @@ public: [[nodiscard]] Main::Session &session() const; [[nodiscard]] not_null controller() const; - [[nodiscard]] PeerData *singlePeer() const; + [[nodiscard]] Window::SeparateId windowId() const; [[nodiscard]] bool isPrimary() const; [[nodiscard]] bool isMainSectionShown() const; [[nodiscard]] bool isThirdSectionShown() const; @@ -350,10 +351,6 @@ private: int _thirdColumnWidth = 0; Ui::Animations::Simple _a_dialogsWidth; - const base::unique_qptr _sideShadow; - object_ptr _thirdShadow = { nullptr }; - object_ptr _firstColumnResizeArea = { nullptr }; - object_ptr _thirdColumnResizeArea = { nullptr }; const base::unique_qptr _dialogs; const base::unique_qptr _history; object_ptr _mainSection = { nullptr }; @@ -361,6 +358,11 @@ private: std::shared_ptr _thirdSectionFromStack; std::unique_ptr _connecting; + const base::unique_qptr _sideShadow; + object_ptr _thirdShadow = { nullptr }; + object_ptr _firstColumnResizeArea = { nullptr }; + object_ptr _thirdColumnResizeArea = { nullptr }; + base::weak_ptr _currentCall; base::weak_ptr _currentGroupCall; rpl::lifetime _currentCallLifetime; diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index 2e705713f..df1f2ef25 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -269,13 +269,11 @@ void MainWindow::setupMain( auto created = object_ptr(bodyWidget(), sessionController()); clearWidgets(); _main = std::move(created); - if (const auto peer = singlePeer()) { - updateControlsGeometry(); - _main->controller()->showPeerHistory( - peer, - Window::SectionShow::Way::ClearStack, - singlePeerShowAtMsgId); - } + updateControlsGeometry(); + Ui::SendPendingMoveResizeEvents(_main); + _main->controller()->showByInitialId( + Window::SectionShow::Way::ClearStack, + singlePeerShowAtMsgId); if (_passcodeLock) { _main->hide(); } else { diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 0dde74e30..a6320b61e 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -6158,7 +6158,7 @@ Window::SessionController *OverlayWidget::findWindow(bool switchTo) const { if (switchTo) { auto controllerPtr = (Window::SessionController*)nullptr; - const auto account = &_session->account(); + const auto account = not_null(&_session->account()); const auto sessionWindow = Core::App().windowFor(account); const auto anyWindow = (sessionWindow && &sessionWindow->account() == account) diff --git a/Telegram/SourceFiles/settings/settings_information.cpp b/Telegram/SourceFiles/settings/settings_information.cpp index 7be04db17..911d44942 100644 --- a/Telegram/SourceFiles/settings/settings_information.cpp +++ b/Telegram/SourceFiles/settings/settings_information.cpp @@ -983,13 +983,13 @@ void AccountsList::rebuild() { _reorder->finishReordering(); if (newWindow) { _closeRequests.fire({}); - Core::App().ensureSeparateWindowForAccount( - account); + Core::App().ensureSeparateWindowFor(account); } Core::App().domain().maybeActivate(account); } }; - if (const auto window = Core::App().separateWindowForAccount(account)) { + if (const auto window = Core::App().separateWindowFor( + account)) { _closeRequests.fire({}); window->activate(); } else { diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index 1276b264f..e50ffd737 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/platform/ui_platform_window.h" #include "platform/platform_window_title.h" #include "history/history.h" +#include "window/window_separate_id.h" #include "window/window_session_controller.h" #include "window/window_lock_widgets.h" #include "window/window_controller.h" @@ -387,8 +388,8 @@ Main::Account &MainWindow::account() const { return _controller->account(); } -PeerData *MainWindow::singlePeer() const { - return _controller->singlePeer(); +Window::SeparateId MainWindow::id() const { + return _controller->id(); } bool MainWindow::isPrimary() const { @@ -602,20 +603,27 @@ WindowPosition MainWindow::initialPosition() const { ? Core::AdjustToScale( Core::App().settings().windowPosition(), u"Window"_q) - : active->widget()->nextInitialChildPosition(isPrimary()); + : active->widget()->nextInitialChildPosition(id()); } -WindowPosition MainWindow::nextInitialChildPosition(bool primary) { +WindowPosition MainWindow::nextInitialChildPosition(SeparateId childId) { const auto rect = geometry().marginsRemoved(frameMargins()); const auto position = rect.topLeft(); const auto adjust = [&](int value) { - return primary ? value : (value * 3 / 4); + return (value * 3 / 4); }; + const auto secondaryWithChatsList = !childId.primary() && childId.hasChatsList(); const auto width = OptionNewWindowsSizeAsFirst.value() ? Core::App().settings().windowPosition().w + : childId.primary() + ? st::windowDefaultWidth + : childId.hasChatsList() + ? (st::columnMinimalWidthLeft + adjust(st::windowDefaultWidth)) : adjust(st::windowDefaultWidth); const auto height = OptionNewWindowsSizeAsFirst.value() ? Core::App().settings().windowPosition().h + : childId.primary() + ? st::windowDefaultHeight : adjust(st::windowDefaultHeight); const auto skip = ChildSkip(); const auto delta = _lastChildIndex diff --git a/Telegram/SourceFiles/window/main_window.h b/Telegram/SourceFiles/window/main_window.h index 49f73e6bb..85d495e7e 100644 --- a/Telegram/SourceFiles/window/main_window.h +++ b/Telegram/SourceFiles/window/main_window.h @@ -33,6 +33,7 @@ class Controller; class SessionController; class TitleWidget; struct TermsLock; +struct SeparateId; [[nodiscard]] const QImage &Logo(); [[nodiscard]] const QImage &LogoNoMargin(); @@ -66,7 +67,7 @@ public: [[nodiscard]] Window::Controller &controller() const { return *_controller; } - [[nodiscard]] PeerData *singlePeer() const; + [[nodiscard]] Window::SeparateId id() const; [[nodiscard]] bool isPrimary() const; [[nodiscard]] Main::Account &account() const; [[nodiscard]] Window::SessionController *sessionController() const; @@ -200,7 +201,7 @@ private: [[nodiscard]] Core::WindowPosition initialPosition() const; [[nodiscard]] Core::WindowPosition nextInitialChildPosition( - bool primary); + SeparateId childId); [[nodiscard]] QRect countInitialGeometry(Core::WindowPosition position); bool computeIsActive() const; diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index 460858972..40f8664f2 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -1096,7 +1096,7 @@ void Manager::openNotificationMessage( && item->isRegular() && (item->out() || (item->mentionsMe() && !history->peer->isUser())); const auto topic = item ? item->topic() : nullptr; - const auto separate = Core::App().separateWindowForPeer(history->peer); + const auto separate = Core::App().separateWindowFor(history->peer); const auto window = separate ? separate->sessionController() : history->session().tryResolveWindow(); diff --git a/Telegram/SourceFiles/window/window_controller.cpp b/Telegram/SourceFiles/window/window_controller.cpp index abe2fa3dc..69bca734f 100644 --- a/Telegram/SourceFiles/window/window_controller.cpp +++ b/Telegram/SourceFiles/window/window_controller.cpp @@ -28,7 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "window/themes/window_theme_editor.h" #include "ui/boxes/confirm_box.h" -#include "data/data_peer.h" +#include "data/data_thread.h" #include "apiwrap.h" // ApiWrap::acceptTerms. #include "styles/style_layers.h" @@ -93,23 +93,18 @@ Show::operator bool() const { } // namespace -Controller::Controller() : Controller(CreateArgs{}) { +Controller::Controller() : Controller(CreateArgs{ nullptr }) { } -Controller::Controller(not_null account) -: Controller(CreateArgs{}) { - showAccount(account); -} - -Controller::Controller( - not_null singlePeer, - MsgId showAtMsgId) -: Controller(CreateArgs{ singlePeer.get() }) { - showAccount(&singlePeer->account(), showAtMsgId); +Controller::Controller(SeparateId id, MsgId showAtMsgId) +: Controller(CreateArgs{ id }) { + if (id) { + showAccount(id.account, showAtMsgId); + } } Controller::Controller(CreateArgs &&args) -: _singlePeer(args.singlePeer) +: _id(args.id) , _isActiveTimer([=] { updateIsActive(); }) , _widget(this) , _adaptive(std::make_unique()) { @@ -125,6 +120,20 @@ Controller::~Controller() { _sessionController = nullptr; } +SeparateId Controller::id() const { + return _id; +} + +bool Controller::isPrimary() const { + return _id.primary(); +} + +Main::Account &Controller::account() const { + Expects(_id.account != nullptr); + + return *_id.account; +} + void Controller::showAccount(not_null account) { showAccount(account, ShowAtUnreadMsgId); } @@ -132,20 +141,22 @@ void Controller::showAccount(not_null account) { void Controller::showAccount( not_null account, MsgId singlePeerShowAtMsgId) { - Expects(isPrimary() || &_singlePeer->account() == account); + Expects(isPrimary() || _id.account == account); - const auto prevSessionUniqueId = (_account && _account->sessionExists()) - ? _account->session().uniqueId() + const auto prevAccount = _id.account; + const auto prevSession = maybeSession(); + const auto prevSessionUniqueId = prevSession + ? prevSession->uniqueId() : 0; _accountLifetime.destroy(); - _account = account; - Core::App().checkWindowAccount(this); + _id.account = account; + Core::App().checkWindowId(this); - const auto updateOnlineOfPrevSesssion = crl::guard(_account, [=] { + const auto updateOnlineOfPrevSesssion = crl::guard(account, [=] { if (!prevSessionUniqueId) { return; } - for (auto &[index, account] : _account->domain().accounts()) { + for (auto &[index, account] : _id.account->domain().accounts()) { if (const auto anotherSession = account->maybeSession()) { if (anotherSession->uniqueId() == prevSessionUniqueId) { anotherSession->updates().updateOnline(crl::now()); @@ -155,12 +166,15 @@ void Controller::showAccount( } }); - _account->sessionValue( - ) | rpl::start_with_next([=](Main::Session *session) { - if (!isPrimary() && (&_singlePeer->session() != session)) { + if (!isPrimary()) { + _id.account->sessionChanges( + ) | rpl::start_with_next([=](Main::Session *session) { Core::App().closeWindow(this); - return; - } + }, _accountLifetime); + } + + _id.account->sessionValue( + ) | rpl::start_with_next([=](Main::Session *session) { const auto was = base::take(_sessionController); _sessionController = session ? std::make_unique(session, this) @@ -205,10 +219,6 @@ void Controller::showAccount( }, _accountLifetime); } -PeerData *Controller::singlePeer() const { - return _singlePeer; -} - void Controller::setupSideBar() { Expects(_sessionController != nullptr); @@ -321,7 +331,7 @@ void Controller::finishFirstShow() { } Main::Session *Controller::maybeSession() const { - return _account ? _account->maybeSession() : nullptr; + return _id.account ? _id.account->maybeSession() : nullptr; } auto Controller::sessionControllerValue() const @@ -356,7 +366,7 @@ void Controller::setupPasscodeLock() { } void Controller::clearPasscodeLock() { - if (!_account) { + if (!_id) { showAccount(&Core::App().activeAccount()); } else { _widget.clearPasscodeLock(); @@ -482,7 +492,7 @@ void Controller::invokeForSessionController( PeerData *singlePeer, Fn)> &&callback) { const auto separateWindow = singlePeer - ? Core::App().separateWindowForPeer(singlePeer) + ? Core::App().separateWindowFor(not_null(singlePeer)) : nullptr; const auto separateSession = separateWindow ? separateWindow->sessionController() @@ -490,7 +500,7 @@ void Controller::invokeForSessionController( if (separateSession) { return callback(separateSession); } - _account->domain().activate(std::move(account)); + _id.account->domain().activate(std::move(account)); if (_sessionController) { callback(_sessionController.get()); } diff --git a/Telegram/SourceFiles/window/window_controller.h b/Telegram/SourceFiles/window/window_controller.h index eadd32e2f..3eb6184e9 100644 --- a/Telegram/SourceFiles/window/window_controller.h +++ b/Telegram/SourceFiles/window/window_controller.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwindow.h" #include "window/window_adaptive.h" +#include "window/window_separate_id.h" namespace Main { class Account; @@ -36,32 +37,20 @@ namespace Window { class Controller final : public base::has_weak_ptr { public: Controller(); - explicit Controller(not_null account); - Controller( - not_null singlePeer, - MsgId showAtMsgId); + Controller(SeparateId id, MsgId showAtMsgId); ~Controller(); Controller(const Controller &other) = delete; Controller &operator=(const Controller &other) = delete; void showAccount(not_null account); - [[nodiscard]] PeerData *singlePeer() const; - [[nodiscard]] bool isPrimary() const { - return (singlePeer() == nullptr); - } + [[nodiscard]] SeparateId id() const; + [[nodiscard]] bool isPrimary() const; [[nodiscard]] not_null<::MainWindow*> widget() { return &_widget; } - [[nodiscard]] Main::Account &account() const { - Expects(_account != nullptr); - - return *_account; - } - [[nodiscard]] Main::Account *maybeAccount() const { - return _account; - } + [[nodiscard]] Main::Account &account() const; [[nodiscard]] Main::Session *maybeSession() const; [[nodiscard]] SessionController *sessionController() const { return _sessionController.get(); @@ -155,7 +144,7 @@ public: private: struct CreateArgs { - PeerData *singlePeer = nullptr; + SeparateId id; }; explicit Controller(CreateArgs &&args); @@ -173,8 +162,7 @@ private: void showTermsDecline(); void showTermsDelete(); - PeerData *_singlePeer = nullptr; - Main::Account *_account = nullptr; + SeparateId _id; base::Timer _isActiveTimer; ::MainWindow _widget; const std::unique_ptr _adaptive; diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index bcddeda4d..194d366c1 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/unread_badge_paint.h" #include "ui/vertical_list.h" +#include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" @@ -551,9 +552,15 @@ void MainMenu::setupArchive() { const auto folder = [=] { return controller->session().data().folderLoaded(Data::Folder::kId); }; - const auto showArchive = [=] { + const auto showArchive = [=](Qt::KeyboardModifiers modifiers) { if (const auto f = folder()) { - controller->openFolder(f); + if (modifiers & Qt::ControlModifier) { + controller->showInNewWindow(Window::SeparateId( + Window::SeparateType::Archive, + &controller->session())); + } else { + controller->openFolder(f); + } controller->window().hideSettingsAndLayer(); } }; @@ -583,7 +590,7 @@ void MainMenu::setupArchive() { button->clicks( ) | rpl::start_with_next([=](Qt::MouseButton which) { if (which == Qt::LeftButton) { - showArchive(); + showArchive(button->clickModifiers()); return; } else if (which != Qt::RightButton) { return; @@ -591,35 +598,13 @@ void MainMenu::setupArchive() { _contextMenu = base::make_unique_q( this, st::popupMenuExpandedSeparator); - const auto addAction = PeerMenuCallback([&]( - PeerMenuCallback::Args a) { - return _contextMenu->addAction( - a.text, - std::move(a.handler), - a.icon); - }); - - const auto hide = [=] { - controller->session().settings().setArchiveInMainMenu(false); - controller->session().saveSettingsDelayed(); - controller->window().hideSettingsAndLayer(); - }; - addAction( - tr::lng_context_archive_to_list(tr::now), - std::move(hide), - &st::menuIconFromMainMenu); - - MenuAddMarkAsReadChatListAction( - controller, - [f = folder()] { return f->chatsList(); }, - addAction); - - _contextMenu->addSeparator(); - Settings::PreloadArchiveSettings(&controller->session()); - addAction(tr::lng_context_archive_settings(tr::now), [=] { - controller->show(Box(Settings::ArchiveSettingsBox, controller)); - }, &st::menuIconManage); - + Window::FillDialogsEntryMenu( + _controller, + Dialogs::EntryState{ + .key = folder(), + .section = Dialogs::EntryState::Section::ContextMenu, + }, + Ui::Menu::CreateAddActionCallback(_contextMenu)); _contextMenu->popup(QCursor::pos()); }, button->lifetime()); diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index d3aecb1c5..712e022b0 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -59,6 +59,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item_helpers.h" // GetErrorTextForSending. #include "history/view/history_view_context_menu.h" +#include "window/window_separate_id.h" #include "window/window_session_controller.h" #include "window/window_controller.h" #include "settings/settings_advanced.h" @@ -650,20 +651,48 @@ void Filler::addToggleUnreadMark() { } void Filler::addNewWindow() { + const auto controller = _controller; + if (_folder) { + _addAction(tr::lng_context_new_window(tr::now), [=] { + Ui::PreventDelayedActivation(); + controller->showInNewWindow(SeparateId( + SeparateType::Archive, + &controller->session())); + }, &st::menuIconNewWindow); + AddSeparatorAndShiftUp(_addAction); + return; + } else if (const auto weak = base::make_weak(_sublist)) { + _addAction(tr::lng_context_new_window(tr::now), [=] { + Ui::PreventDelayedActivation(); + if (const auto sublist = weak.get()) { + const auto peer = sublist->peer(); + controller->showInNewWindow(SeparateId( + SeparateType::SavedSublist, + peer->owner().history(peer))); + } + }, &st::menuIconNewWindow); + AddSeparatorAndShiftUp(_addAction); + return; + } const auto history = _request.key.history(); if (!_peer - || _topic - || _peer->isForum() || (history && history->useTopPromotion() && !history->topPromotionType().isEmpty())) { return; } const auto peer = _peer; - const auto controller = _controller; + const auto thread = _topic + ? not_null(_topic) + : _peer->owner().history(_peer); + const auto weak = base::make_weak(thread); _addAction(tr::lng_context_new_window(tr::now), [=] { Ui::PreventDelayedActivation(); - controller->showInNewWindow(peer); + if (const auto strong = weak.get()) { + controller->showInNewWindow(SeparateId( + peer->isForum() ? SeparateType::Forum : SeparateType::Chat, + strong)); + } }, &st::menuIconNewWindow); AddSeparatorAndShiftUp(_addAction); } @@ -1253,6 +1282,12 @@ void Filler::addViewAsMessages() { FullMsgId(), }, callback, QApplication::activePopupWidget()); return true; + } else if (base::IsCtrlPressed()) { + Ui::PreventDelayedActivation(); + controller->showInNewWindow(SeparateId( + SeparateType::Chat, + peer->owner().history(peer))); + return true; } return false; }; @@ -1432,27 +1467,39 @@ void Filler::fillArchiveActions() { if (_folder->id() != Data::Folder::kId) { return; } + addNewWindow(); + const auto controller = _controller; const auto hidden = controller->session().settings().archiveCollapsed(); - const auto text = hidden - ? tr::lng_context_archive_expand(tr::now) - : tr::lng_context_archive_collapse(tr::now); - _addAction(text, [=] { - controller->session().settings().setArchiveCollapsed(!hidden); - controller->session().saveSettingsDelayed(); - }, hidden ? &st::menuIconExpand : &st::menuIconCollapse); - - _addAction(tr::lng_context_archive_to_menu(tr::now), [=] { - controller->showToast({ - .text = { tr::lng_context_archive_to_menu_info(tr::now) }, - .st = &st::windowArchiveToast, - .duration = kArchivedToastDuration, - }); - - controller->session().settings().setArchiveInMainMenu( - !controller->session().settings().archiveInMainMenu()); - controller->session().saveSettingsDelayed(); - }, &st::menuIconToMainMenu); + { + const auto text = hidden + ? tr::lng_context_archive_expand(tr::now) + : tr::lng_context_archive_collapse(tr::now); + _addAction(text, [=] { + controller->session().settings().setArchiveCollapsed(!hidden); + controller->session().saveSettingsDelayed(); + }, hidden ? &st::menuIconExpand : &st::menuIconCollapse); + } + const auto inmenu = controller->session().settings().archiveInMainMenu(); + { + const auto text = inmenu + ? tr::lng_context_archive_to_list(tr::now) + : tr::lng_context_archive_to_menu(tr::now); + _addAction(text, [=] { + if (!inmenu) { + controller->showToast({ + .text = { + tr::lng_context_archive_to_menu_info(tr::now) + }, + .st = &st::windowArchiveToast, + .duration = kArchivedToastDuration, + }); + } + controller->session().settings().setArchiveInMainMenu(!inmenu); + controller->session().saveSettingsDelayed(); + controller->window().hideSettingsAndLayer(); + }, inmenu ? &st::menuIconFromMainMenu : &st::menuIconToMainMenu); + } MenuAddMarkAsReadChatListAction( controller, @@ -1460,6 +1507,7 @@ void Filler::fillArchiveActions() { _addAction); _addAction({ .isSeparator = true }); + Settings::PreloadArchiveSettings(&controller->session()); _addAction(tr::lng_context_archive_settings(tr::now), [=] { controller->show(Box(Settings::ArchiveSettingsBox, controller)); @@ -1467,6 +1515,7 @@ void Filler::fillArchiveActions() { } void Filler::fillSavedSublistActions() { + addNewWindow(); addTogglePin(); } @@ -1983,17 +2032,17 @@ QPointer ShowForwardMessagesBox( ForwardToSelf(show, draft); return true; } - auto controller = Core::App().windowFor(peer); + const auto id = SeparateId( + (peer->isForum() + ? SeparateType::Forum + : SeparateType::Chat), + thread); + auto controller = Core::App().windowFor(id); if (!controller) { return false; } if (controller->maybeSession() != &peer->session()) { - controller = peer->isForum() - ? Core::App().ensureSeparateWindowForAccount( - &peer->account()) - : Core::App().ensureSeparateWindowForPeer( - peer, - ShowAtUnreadMsgId); + controller = Core::App().ensureSeparateWindowFor(id); if (controller->maybeSession() != &peer->session()) { return false; } diff --git a/Telegram/SourceFiles/window/window_separate_id.cpp b/Telegram/SourceFiles/window/window_separate_id.cpp new file mode 100644 index 000000000..f2ed89183 --- /dev/null +++ b/Telegram/SourceFiles/window/window_separate_id.cpp @@ -0,0 +1,77 @@ +/* +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 "window/window_separate_id.h" + +#include "data/data_folder.h" +#include "data/data_peer.h" +#include "data/data_saved_messages.h" +#include "data/data_session.h" +#include "data/data_thread.h" +#include "history/history.h" +#include "main/main_account.h" +#include "main/main_session.h" + +namespace Window { + +SeparateId::SeparateId(std::nullptr_t) { +} + +SeparateId::SeparateId(not_null account) +: account(account) { +} + +SeparateId::SeparateId(SeparateType type, not_null session) +: type(type) +, account(&session->account()) { +} + +SeparateId::SeparateId(SeparateType type, not_null thread) +: type(type) +, account(&thread->session().account()) +, thread(thread) { +} + +SeparateId::SeparateId(not_null thread) +: SeparateId(SeparateType::Chat, thread) { +} + +SeparateId::SeparateId(not_null peer) +: SeparateId(SeparateType::Chat, peer->owner().history(peer)) { +} + +bool SeparateId::primary() const { + return (type == SeparateType::Primary); +} + +Data::Thread *SeparateId::chat() const { + return (type == SeparateType::Chat) ? thread : nullptr; +} + +Data::Forum *SeparateId::forum() const { + return (type == SeparateType::Forum) ? thread->asForum() : nullptr; +} + +Data::Folder *SeparateId::folder() const { + return (type == SeparateType::Archive) + ? account->session().data().folder(Data::Folder::kId).get() + : nullptr; +} + +Data::SavedSublist *SeparateId::sublist() const { + return (type == SeparateType::SavedSublist) + ? thread->owner().savedMessages().sublist(thread->peer()).get() + : nullptr; +} + +bool SeparateId::hasChatsList() const { + return (type == SeparateType::Primary) + || (type == SeparateType::Archive) + || (type == SeparateType::Forum); +} + +} // namespace Window diff --git a/Telegram/SourceFiles/window/window_separate_id.h b/Telegram/SourceFiles/window/window_separate_id.h new file mode 100644 index 000000000..bcbe72860 --- /dev/null +++ b/Telegram/SourceFiles/window/window_separate_id.h @@ -0,0 +1,69 @@ +/* +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 + +class PeerData; + +namespace Data { +class Thread; +class Folder; +class Forum; +class SavedSublist; +} // namespace Data + +namespace Main { +class Account; +class Session; +} // namespace Main + +namespace Window { + +enum class SeparateType { + Primary, + Archive, + Chat, + Forum, + SavedSublist, +}; + +struct SeparateId { + SeparateId(std::nullptr_t); + SeparateId(not_null account); + SeparateId(SeparateType type, not_null session); + SeparateId(SeparateType type, not_null thread); + SeparateId(not_null thread); + SeparateId(not_null peer); + + SeparateType type = SeparateType::Primary; + Main::Account *account = nullptr; + Data::Thread *thread = nullptr; // For types except Main and Archive. + + [[nodiscard]] bool valid() const { + return account != nullptr; + } + explicit operator bool() const { + return valid(); + } + + [[nodiscard]] bool primary() const; + [[nodiscard]] Data::Thread *chat() const; + [[nodiscard]] Data::Forum *forum() const; + [[nodiscard]] Data::Folder *folder() const; + [[nodiscard]] Data::SavedSublist *sublist() const; + + [[nodiscard]] bool hasChatsList() const; + + friend inline auto operator<=>( + const SeparateId &, + const SeparateId &) = default; + friend inline bool operator==( + const SeparateId &, + const SeparateId &) = default; +}; + +} // namespace Window diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 59d12ab35..34d3bf489 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_chat_preview.h" #include "window/window_controller.h" #include "window/window_filters_menu.h" +#include "window/window_separate_id.h" #include "info/channel_statistics/earn/info_earn_inner_widget.h" #include "info/info_memento.h" #include "info/info_controller.h" @@ -26,11 +27,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL //#include "history/view/reactions/history_view_reactions_button.h" #include "history/view/history_view_replies_section.h" #include "history/view/history_view_scheduled_section.h" +#include "history/view/history_view_sublist_section.h" #include "media/player/media_player_instance.h" #include "media/view/media_view_open_common.h" #include "data/stickers/data_custom_emoji.h" #include "data/data_document_resolver.h" #include "data/data_download_manager.h" +#include "data/data_saved_messages.h" #include "data/data_session.h" #include "data/data_file_origin.h" #include "data/data_folder.h" @@ -1100,6 +1103,36 @@ void SessionNavigation::showPeerHistory( showPeerHistory(history->peer->id, params, msgId); } +void SessionNavigation::showByInitialId( + const SectionShow ¶ms, + MsgId msgId) { + const auto parent = parentController(); + const auto id = parent->window().id(); + auto instant = params; + instant.animated = anim::type::instant; + switch (id.type) { + case SeparateType::Archive: + clearSectionStack(instant); + parent->openFolder(id.folder()); + break; + case SeparateType::Forum: + clearSectionStack(instant); + parent->showForum(id.forum(), instant); + break; + case SeparateType::Primary: + clearSectionStack(instant); + break; + case SeparateType::Chat: + showThread(id.thread, msgId, instant); + break; + case SeparateType::SavedSublist: + showSection( + std::make_shared(id.sublist()), + instant); + break; + } +} + void SessionNavigation::showSettings( Settings::Type type, const SectionShow ¶ms) { @@ -1182,6 +1215,7 @@ SessionController::SessionController( std::make_unique(session)) , _chatPreviewManager(std::make_unique(this)) , _isPrimary(window->isPrimary()) +, _hasDialogs(window->id().hasChatsList()) , _sendingAnimation( std::make_unique(this)) , _tabbedSelector( @@ -1191,6 +1225,7 @@ SessionController::SessionController( GifPauseReason::TabbedPanel)) , _invitePeekTimer([=] { checkInvitePeek(); }) , _activeChatsFilter(session->data().chatsFilters().defaultId()) +, _openedFolder(window->id().folder()) , _defaultChatTheme(std::make_shared()) , _chatStyle(std::make_unique(session->colorIndicesValue())) , _giftPremiumValidator(this) { @@ -1373,8 +1408,8 @@ void SessionController::suggestArchiveAndMute() { })); } -PeerData *SessionController::singlePeer() const { - return _window->singlePeer(); +SeparateId SessionController::windowId() const { + return _window->id(); } bool SessionController::isPrimary() const { @@ -1466,7 +1501,7 @@ void SessionController::setupShortcuts() { if (account == &session().account()) { return false; } - const auto window = app->separateWindowForAccount(account); + const auto window = app->separateWindowFor(account); if (window) { window->activate(); } else { @@ -1523,7 +1558,9 @@ void SessionController::checkOpenedFilter() { } void SessionController::activateFirstChatsFilter() { - if (_filtersActivated || !session().data().chatsFilters().loaded()) { + if (_filtersActivated + || !isPrimary() + || !session().data().chatsFilters().loaded()) { return; } _filtersActivated = true; @@ -1536,8 +1573,24 @@ bool SessionController::uniqueChatsInSearchResults() const { && !_searchInChat.current(); } +bool SessionController::openFolderInDifferentWindow( + not_null folder) { + const auto id = SeparateId(SeparateType::Archive, &session()); + if (const auto separate = Core::App().separateWindowFor(id)) { + if (separate == _window) { + return false; + } + separate->sessionController()->showByInitialId(); + separate->activate(); + return true; + } + return false; +} + void SessionController::openFolder(not_null folder) { - if (_openedFolder.current() != folder) { + if (openFolderInDifferentWindow(folder)) { + return; + } else if (_openedFolder.current() != folder) { resetFakeUnreadWhileOpened(); } if (activeChatsFilterCurrent() != 0) { @@ -1550,22 +1603,44 @@ void SessionController::openFolder(not_null folder) { } void SessionController::closeFolder() { + if (_openedFolder.current() + && windowId().type == SeparateType::Archive) { + Core::App().closeWindow(_window); + return; + } _openedFolder = nullptr; } +bool SessionController::showForumInDifferentWindow( + not_null forum, + const SectionShow ¶ms) { + const auto window = Core::App().windowForShowingForum(forum); + if (window == _window) { + return false; + } else if (window) { + window->sessionController()->showForum(forum, params); + window->activate(); + return true; + } else if (windowId().hasChatsList()) { + return false; + } + const auto account = not_null(&session().account()); + auto primary = Core::App().separateWindowFor(account); + if (!primary) { + Core::App().domain().activate(account); + primary = Core::App().separateWindowFor(account); + } + if (primary && &primary->account() == account) { + primary->sessionController()->showForum(forum, params); + primary->activate(); + } + return true; +} + void SessionController::showForum( not_null forum, const SectionShow ¶ms) { - if (!isPrimary()) { - auto primary = Core::App().windowFor(&session().account()); - if (&primary->account() != &session().account()) { - Core::App().domain().activate(&session().account()); - primary = Core::App().windowFor(&session().account()); - } - if (&primary->account() == &session().account()) { - primary->sessionController()->showForum(forum, params); - } - primary->activate(); + if (showForumInDifferentWindow(forum, params)) { return; } _shownForumLifetime.destroy(); @@ -1598,6 +1673,18 @@ void SessionController::showForum( } void SessionController::closeForum() { + if (const auto forum = _shownForum.current()) { + const auto id = windowId(); + if (id.type == SeparateType::Forum) { + const auto initial = id.thread->asForum(); + if (!initial || initial == forum) { + Core::App().closeWindow(_window); + } else { + showForum(initial); + } + return; + } + } _shownForumLifetime.destroy(); _shownForum = nullptr; } @@ -1889,7 +1976,7 @@ int SessionController::dialogsSmallColumnWidth() const { } int SessionController::minimalThreeColumnWidth() const { - return (_isPrimary ? st::columnMinimalWidthLeft : 0) + return (_hasDialogs ? st::columnMinimalWidthLeft : 0) + st::columnMinimalWidthMain + st::columnMinimalWidthThird; } @@ -1903,7 +1990,7 @@ auto SessionController::computeColumnLayout() const -> ColumnLayout { auto useOneColumnLayout = [&] { auto minimalNormal = st::columnMinimalWidthLeft + st::columnMinimalWidthMain; - if (_isPrimary && bodyWidth < minimalNormal) { + if (_hasDialogs && bodyWidth < minimalNormal) { return true; } return false; @@ -1945,7 +2032,7 @@ auto SessionController::computeColumnLayout() const -> ColumnLayout { } int SessionController::countDialogsWidthFromRatio(int bodyWidth) const { - if (!_isPrimary) { + if (!_hasDialogs) { return 0; } const auto nochat = !mainSectionShown(); @@ -1979,8 +2066,8 @@ SessionController::ShrinkResult SessionController::shrinkDialogsAndThirdColumns( if (thirdWidthNew < st::columnMinimalWidthThird) { thirdWidthNew = st::columnMinimalWidthThird; dialogsWidthNew = bodyWidth - thirdWidthNew - chatWidth; - Assert(!_isPrimary || dialogsWidthNew >= st::columnMinimalWidthLeft); - } else if (_isPrimary && dialogsWidthNew < st::columnMinimalWidthLeft) { + Assert(!_hasDialogs || dialogsWidthNew >= st::columnMinimalWidthLeft); + } else if (_hasDialogs && dialogsWidthNew < st::columnMinimalWidthLeft) { dialogsWidthNew = st::columnMinimalWidthLeft; thirdWidthNew = bodyWidth - dialogsWidthNew - chatWidth; Assert(thirdWidthNew >= st::columnMinimalWidthThird); @@ -2089,9 +2176,11 @@ void SessionController::closeThirdSection() { } } -bool SessionController::canShowSeparateWindow( - not_null peer) const { - return !peer->isForum() && peer->computeUnavailableReason().isEmpty(); +bool SessionController::canShowSeparateWindow(SeparateId id) const { + if (const auto thread = id.thread) { + return thread->peer()->computeUnavailableReason().isEmpty(); + } + return true; } void SessionController::showPeer(not_null peer, MsgId msgId) { @@ -2314,21 +2403,20 @@ void SessionController::clearChooseReportMessages() const { } void SessionController::showInNewWindow( - not_null peer, + SeparateId id, MsgId msgId) { - if (!canShowSeparateWindow(peer)) { - showThread( - peer->owner().history(peer), - msgId, - Window::SectionShow::Way::ClearStack); + if (!canShowSeparateWindow(id)) { + Assert(id.thread != nullptr); + showThread(id.thread, msgId, SectionShow::Way::ClearStack); return; } const auto active = activeChatCurrent(); - const auto fromActive = active.history() - ? (active.history()->peer == peer) + // windows check active forum / active archive + const auto fromActive = active.thread() + ? (active.thread() == id.thread) : false; const auto toSeparate = [=] { - Core::App().ensureSeparateWindowForPeer(peer, msgId); + Core::App().ensureSeparateWindowFor(id, msgId); }; if (fromActive) { window().preventOrInvoke([=] { @@ -2485,6 +2573,9 @@ FilterId SessionController::activeChatsFilterCurrent() const { void SessionController::setActiveChatsFilter( FilterId id, const SectionShow ¶ms) { + if (!isPrimary()) { + return; + } const auto changed = (activeChatsFilterCurrent() != id); if (changed) { resetFakeUnreadWhileOpened(); diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 35e690903..d738a048e 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -91,6 +91,7 @@ class FiltersMenu; class ChatPreviewManager; struct PeerByLinkInfo; +struct SeparateId; struct PeerThemeOverride { PeerData *peer = nullptr; @@ -232,6 +233,10 @@ public: ShowAtUnreadMsgId); } + void showByInitialId( + const SectionShow ¶ms = SectionShow::Way::ClearStack, + MsgId msgId = ShowAtUnreadMsgId); + void showSettings( Settings::Type type, const SectionShow ¶ms = SectionShow()); @@ -326,7 +331,7 @@ public: [[nodiscard]] Controller &window() const { return *_window; } - [[nodiscard]] PeerData *singlePeer() const; + [[nodiscard]] SeparateId windowId() const; [[nodiscard]] bool isPrimary() const; [[nodiscard]] not_null<::MainWindow*> widget() const; [[nodiscard]] not_null content() const; @@ -432,7 +437,7 @@ public: void resizeForThirdSection(); void closeThirdSection(); - [[nodiscard]] bool canShowSeparateWindow(not_null peer) const; + [[nodiscard]] bool canShowSeparateWindow(SeparateId id) const; void showPeer(not_null peer, MsgId msgId = ShowAtUnreadMsgId); void startOrJoinGroupCall(not_null peer); @@ -509,7 +514,7 @@ public: void clearChooseReportMessages() const; void showInNewWindow( - not_null peer, + SeparateId id, MsgId msgId = ShowAtUnreadMsgId); void toggleChooseChatTheme( @@ -667,10 +672,16 @@ private: void checkNonPremiumLimitToastDownload(DocumentId id); void checkNonPremiumLimitToastUpload(FullMsgId id); + bool openFolderInDifferentWindow(not_null folder); + bool showForumInDifferentWindow( + not_null forum, + const SectionShow ¶ms); + const not_null _window; const std::unique_ptr _emojiInteractions; const std::unique_ptr _chatPreviewManager; const bool _isPrimary = false; + const bool _hasDialogs = false; mutable std::shared_ptr _cachedShow; From 374b3c68ac85876b889878a4c8754bab5238ceaf Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 29 Jun 2024 10:49:15 +0400 Subject: [PATCH 132/142] Fix cancel search with a similar channel open. Fixes #28079. --- Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 82c8f0384..922b673a0 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -3668,7 +3668,11 @@ bool Widget::cancelSearch(CancelSearchOptions options) { _inner->clearFilter(); applySearchState(std::move(updatedState)); if (_suggestions && clearSearchFocus) { + const auto clearLockedFocus = !_searchHasFocus; setInnerFocus(true); + if (clearLockedFocus) { + processSearchFocusChange(); + } } updateForceDisplayWide(); return clearingQuery || clearingInChat || clearSearchFocus; From 4cafacc8dbe4db17ce3878cf1d0037d8a4d7cee3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 29 Jun 2024 11:29:09 +0400 Subject: [PATCH 133/142] Add message effects promo section. --- .../Resources/icons/settings/premium/effects.png | Bin 0 -> 726 bytes .../icons/settings/premium/effects@2x.png | Bin 0 -> 1395 bytes .../icons/settings/premium/effects@3x.png | Bin 0 -> 2093 bytes Telegram/Resources/langs/lang.strings | 2 ++ .../SourceFiles/boxes/premium_preview_box.cpp | 5 +++++ Telegram/SourceFiles/boxes/premium_preview_box.h | 1 + Telegram/SourceFiles/settings/settings.style | 1 + .../SourceFiles/settings/settings_premium.cpp | 13 +++++++++++++ 8 files changed, 22 insertions(+) create mode 100644 Telegram/Resources/icons/settings/premium/effects.png create mode 100644 Telegram/Resources/icons/settings/premium/effects@2x.png create mode 100644 Telegram/Resources/icons/settings/premium/effects@3x.png diff --git a/Telegram/Resources/icons/settings/premium/effects.png b/Telegram/Resources/icons/settings/premium/effects.png new file mode 100644 index 0000000000000000000000000000000000000000..56af6306310d223df7cb87571b3c4d87c7eef94b GIT binary patch literal 726 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfyu$s#WBP} z@M*AZe`caUoY-!Unl%Dkt?~{_1ynlLnusjACSY>AV}d}ZpR&|iuZ0UHd6X?)a3QDZ z$3G4ykwpTLjz_NFJ85gKe@?FG^z1v^Eh_J&r_C!*J9p;r;r~@0Dwj^5?heqXsi|?X zP1t;subnyeJ)_pttgTV^@836{efInNVjH>BPd7!aZ8PXTYBba5!1H-5H$t?oCKw!e zq`&|0!wKh~AAkID{d)Ze+EcqOzjTR8X<_*Bx31MG(M0NK(nr<)#1$W3zSNw5zTZtb zq3h_Q=g-ye^|~!~)V=(2OMu3U606NOYi4pE+I&+-fFr|XR@PRox4T1rP8QstH1WmH zpOR9X3`}}+1;jX5;?{4!{L&#XZ@aXX>oRQtzV@wAYmYykdH%UZ_xuHS^Oj$Jc{fj- zhi(0%jooqU(>F#K&CJ<-_aBd1O3_Z4#sD3$==IlI7`LDJSkcpRuKsO_m7xSr$`5IV z;I=#Goc6}?Z!Bjhxs|p#)kyNg%d*`p^MpE6_TRt1is@s}$}ex<^496`Y`>eQF2JNE z)w_tn>B9T(l{RuF2UCn%ofK!-B`NAn@1Dl3ekI2&(JOIBte*IcNheii*5q-=_4zG7 zT=Ym~!|E8l?>}oUxTmx(^i>hkSg9SlG^p~mEHJ=JuV!t%n#KCQIc9HM`(9faz8@tH zi$b&#`42f9WIFuw$B&GyQ6H8EtrXdI@`CJ#P8X#eKR!QloV3Btz^m6ydG=Yo>E3g+ z%TkSIb{{>IU?9+Mns`R{)7P(4ofh8N^w0Q@K5OS^i)Yb$%0X$$)78&qol`;+02V$m AP5=M^ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/effects@2x.png b/Telegram/Resources/icons/settings/premium/effects@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..5f5e4ada627654bfb021b7e0e9f189bfc7303b94 GIT binary patch literal 1395 zcmV-(1&sQMP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NG-AP12R9Fe^SXn4-Q5e2vN+dE5 z88W71${2YdB#ESu@*v(si#pN+hC4l+1*9kTOS-WX?RtyHF8(YyHFi@k9Mn?|^y-{-Yg;jEvOK&|oh}i!BzF>gecLT3R|gJG;2J z=;`UbzrVk|y$M={g@w(`%p4pXtgWpHDtw)3XlR(2m{?z5zq`Bp`T6tt!|d#A zadEMyrzaPqtgH-*ot>Rr=67jxbMxxz>YJOJKVuMn8XO!nH8rJ>%gf8qJ2^SQMpEY2 z2`w$H($dnWrzc@>9(R9#A8!+Qc6N4|nVCaFLsVscem*BB$JNzUb~N+=IyyQoEG$ru zQi$5xTHqxoCo`0>LU}Rm?d`?J#=@qlsfpoNP}JSs4MPV9ht}3s?3RMg@$qq2SC@x} zhm3&S-QD4(xw#oO_V)G#1qGX%n+kpzkI&D~fq{V(&)3)Y?(PoyA0Hp_@$r-|gQ%&g zfjyQ&4<^pUogFHSy zo{^COX-rHEgr&dm6ZjPs;Vl`GA^2rj}H=;LUy^lyhIj(qlkzI>2Xlajg1Z1 zVy7o2CcM17KsPir7#J9Ukoq$-Gwbc`g~!|5+tAQZdfoc^`Y4PnA|WAxVmmoGA)6N! z6;X~PA|N0Dkhlxb*4NjMii#Q;8IctPSNZz-IzK<(+uJ)eH5JtZB3xlfaZn{>!mF#R z?CflCkW{$dRYeB}2U9o^!q3kS8c4F*+S+(us9CBCg6pTGq(meRBG%N@+}heAr4jpt z;>Go%LV93eKvF=qwzfb;BeAlwf;^}m%+r#^7r@WOI64*F)`tL z)8w;WUS9ajzrhg%5(wz*?CfvA^L--U@|gkwou8iz#?CjE&RJhypPrt6e0=1Z?Ck7t zA6fL+*qFir{PH6@+PJtl0%l`lGdem-U8Sd|vx4Bt3JVLV**6K2Y*0;6wgF*bVL|#3 zR1Y-7Tu>GbGc*t14276mSy=(8zrP=y5XrYKEiDj%!-{|}Yiw-%en8vX+whFS{`&d~ zZ{_9Xc*L6m9TF142W4@P9wkl}jJQd|iNZ{DrlzI<3C}$}JuGZxGE55$FotM#brp>m z;vh{=Wo0E%#xWqR8M3Z2P!YbYJ{qVfI0|&<7Z@KguI1j9{n!n3QTsmQCifCr~+ykR)|`r@=i+>TCJeofq%UNzW@^lQIP%Wrr7`h002ovPDHLkV1gI^ Bca{JE literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/premium/effects@3x.png b/Telegram/Resources/icons/settings/premium/effects@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..30ec7d9eab70de000d84b312e64990e463ba3ec9 GIT binary patch literal 2093 zcmV+|2-5e7P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS?mq|oHRA>e5nq}w}TM)qif4e(T zpIumBfQnf7?EYYb7+-9`4h-;{4T_+mAaC^l7?_a!laqHHt=g*(JQtQ^O+h4za6)RS}dGn?# zz9J+=v1-+-{r&6Ls8ORldGaVT3-J2&>$7Li9yxO4;K75(k01Z}^QQ%Y$EHo2${;{O zip7%hKY~#gEn4*8!GnJi|2=&8aQyi3si~oT)C1ZOBPegk|j$H8Z_wf zRX~Z{I$5?%XD3Qs6#)`n-Ab#^mh}9yV;4$XB^? znk`$laJ0&l=|4}CH*en6t5-X)PiU%Z*RCSsrcIl?FB~vnKnRM}uV4S$w{QLW^%Gj7 zN00U{fh%VJ{{4k)t5&UU+_>S2k0kigvSrJLpxEr$v*EX6#}1)|xA^ekgR2A{5e^+X zWQtg#M2X(Ld#_!)mH=QX!Y#b(_&hcCLw^lsZ*y;W((hkK!Epu`t<1v6)Fh*0|yQW z!7*dTm>m7XZQHiJd-tw`luw^N)kELCdv^y+A5$6taO<+XUcGvvjK?sYA)e~x%a>;d z#+zllaN)uY8#WXKHmU=rkEte2n#ihc+qR+D?c2A{ojb<}Cp?7Qs%q7$g9i`ZxN#!~ z*R=ZX-Mex3b?es6km0voX3UsjA_RxYk2S+Wj6#J9iJt5uhsj zgRJW1%a@7}7BG48WD6tw@QD*Apnu`Q1#J9V|aq*}FVkqtI$)=bybtXXr< zo;{i|>Ui1%B;pQC|GQ$@B2geBhLG2sIdcZ@N3ltRvMqjE4pR1VVCSM37fM--dkxLQ zI*60VRKbD;9nf92Vwg7{2Pu3_IVX)Sig5w+#WXDzanp5Rn`q(U4Y1Qq`~!zj*PYt(1gg`cI0?&Ds`C2VJ%z zc$+tGR_rtY=FFKRTqw4YyDeL`OiJ05Sh&lp085uHRoExMZq#0m8Z3DLr4$jA`<`$a zGGvI1^ytw;MsR6C_n}anHf>t+G%+dDb`g#w+1l>dvSo`5(zX@ki4!NvNYH<>vUl&^ znGz%kUbt|f$Vy(SEGVdG)d=@UOWI4H#r=%IiLE$x?AV~@@v#2z;lqNZ9aUrO*Q=y5Sx@i|Gq-=gysK#ee{1CQB3+iHa2~wqe7DSmG2Zyc{iG zYNe#mE{J96W8~VkYo@>E6p=v9ojdpZ`ST8q8#*uKA5*4GY23K6rDyF^A6+J8Qy-dG zmT;nkCfOieKTFNff^7Tv@#Du8D^`S~9!ZUZIc?d;8|>AqmngjSyjal-4(XC|ZP1_r%^L0?mYz;L zIy7Pk2BSueVjC+F{{XGc5R0TVH*f$JX^2sLfHiaGOf!pD^*G)%HOWX~!-IuRVvma# zFIFB7z}vTP9cIlbB2~OL7o|%R6Zp}iM@gxd@%e?T+u%M*s3CQzUcEX_kKjwX`y0bG^F;(57Q7HI0h8mGE?v6Br<44{)s}9+K`u&kz$CixV#$vK z68wXfV>NkcS}gd3gbN}H6#*2(139;gg0M)pwcIbAzcooW$s)#)1tJSX7KkkHhgjfW X6bGP`k1YYr00000NkvXXu0mjflhyBA literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 95559a1c7..b517e1f8d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2195,6 +2195,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_summary_about_translation" = "Real-time translation of channels and chats into other languages."; "lng_premium_summary_subtitle_business" = "Telegram Business"; "lng_premium_summary_about_business" = "Upgrade your account with business features such as location, opening hours and quick replies."; +"lng_premium_summary_subtitle_effects" = "Message Effects"; +"lng_premium_summary_about_effects" = "Add over 500 animated effects to private messages."; "lng_premium_summary_bottom_subtitle" = "About Telegram Premium"; "lng_premium_summary_bottom_about" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone."; "lng_premium_summary_button" = "Subscribe for {cost} per month"; diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp index 76a2a18b7..f163be612 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -131,6 +131,8 @@ void PreloadSticker(const std::shared_ptr &media) { return tr::lng_premium_summary_subtitle_translation(); case PremiumFeature::Business: return tr::lng_premium_summary_subtitle_business(); + case PremiumFeature::Effects: + return tr::lng_premium_summary_subtitle_effects(); case PremiumFeature::BusinessLocation: return tr::lng_business_subtitle_location(); @@ -192,6 +194,8 @@ void PreloadSticker(const std::shared_ptr &media) { return tr::lng_premium_summary_about_translation(); case PremiumFeature::Business: return tr::lng_premium_summary_about_business(); + case PremiumFeature::Effects: + return tr::lng_premium_summary_about_effects(); case PremiumFeature::BusinessLocation: return tr::lng_business_about_location(); @@ -529,6 +533,7 @@ struct VideoPreviewDocument { case PremiumFeature::Wallpapers: return "wallpapers"; case PremiumFeature::LastSeen: return "last_seen"; case PremiumFeature::MessagePrivacy: return "message_privacy"; + case PremiumFeature::Effects: return "effects"; case PremiumFeature::BusinessLocation: return "business_location"; case PremiumFeature::BusinessHours: return "business_hours"; diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.h b/Telegram/SourceFiles/boxes/premium_preview_box.h index 63b1bd1be..4dae5c30d 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.h +++ b/Telegram/SourceFiles/boxes/premium_preview_box.h @@ -70,6 +70,7 @@ enum class PremiumFeature { LastSeen, MessagePrivacy, Business, + Effects, // Business features. BusinessLocation, diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 5fb976285..6ffd1842a 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -95,6 +95,7 @@ settingsPremiumIconTags: icon {{ "settings/premium/tags", settingsIconFg }}; settingsPremiumIconLastSeen: icon {{ "settings/premium/lastseen", settingsIconFg }}; settingsPremiumIconPrivacy: icon {{ "settings/premium/privacy", settingsIconFg }}; settingsPremiumIconBusiness: icon {{ "settings/premium/market", settingsIconFg }}; +settingsPremiumIconEffects: icon {{ "settings/premium/effects", settingsIconFg }}; settingsStoriesIconOrder: icon {{ "settings/premium/stories_order", premiumButtonBg1 }}; settingsStoriesIconStealth: icon {{ "menu/stealth", premiumButtonBg1 }}; diff --git a/Telegram/SourceFiles/settings/settings_premium.cpp b/Telegram/SourceFiles/settings/settings_premium.cpp index 9a003d3d2..45ef5f63d 100644 --- a/Telegram/SourceFiles/settings/settings_premium.cpp +++ b/Telegram/SourceFiles/settings/settings_premium.cpp @@ -197,6 +197,7 @@ using Order = std::vector; u"animated_userpics"_q, u"premium_stickers"_q, u"business"_q, + u"effects"_q, }; } @@ -377,6 +378,16 @@ using Order = std::vector; true, }, }, + { + u"effects"_q, + Entry{ + &st::settingsPremiumIconEffects, + tr::lng_premium_summary_subtitle_effects(), + tr::lng_premium_summary_about_effects(), + PremiumFeature::Effects, + true, + }, + }, }; } @@ -1601,6 +1612,8 @@ std::vector PremiumFeaturesOrder( return PremiumFeature::RealTimeTranslation; } else if (s == u"wallpapers"_q) { return PremiumFeature::Wallpapers; + } else if (s == u"effects"_q) { + return PremiumFeature::Effects; } return PremiumFeature::kCount; }) | ranges::views::filter([](PremiumFeature type) { From aaa72b7c30c91a11b3d2c7839db5a9640aa27cd2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 29 Jun 2024 11:29:28 +0400 Subject: [PATCH 134/142] Improve system unlock button area. --- Telegram/SourceFiles/boxes/boxes.style | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 364d8f289..fbe98a496 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -292,9 +292,10 @@ passcodeAboutSkip: 7px; passcodeSkip: 23px; passcodeSystemUnlock: IconButton(defaultIconButton) { width: 32px; - height: 32px; - rippleAreaSize: 24px; - rippleAreaPosition: point(4px, 4px); + height: 36px; + iconPosition: point(4px, 4px); + rippleAreaSize: 32px; + rippleAreaPosition: point(0px, 0px); ripple: RippleAnimation(defaultRippleAnimation) { color: lightButtonBgOver; } From bd49887607f3ed99c0e941df01c8425fb92a16ad Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 29 Jun 2024 11:30:08 +0400 Subject: [PATCH 135/142] Fix paid media price tag background. --- Telegram/SourceFiles/boxes/send_files_box.cpp | 15 +++++++++++++++ Telegram/SourceFiles/boxes/send_files_box.h | 1 + .../ui/chat/attach/attach_album_preview.cpp | 5 +++-- .../ui/chat/attach/attach_album_preview.h | 5 +++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 01ecb2b4d..809b6f09f 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -343,6 +343,14 @@ rpl::producer SendFilesBox::Block::itemModifyRequest() const { } } +rpl::producer<> SendFilesBox::Block::orderUpdated() const { + if (_isAlbum) { + const auto album = static_cast(_preview.get()); + return album->orderUpdated(); + } + return rpl::never<>(); +} + void SendFilesBox::Block::setSendWay(Ui::SendFilesWay way) { if (!_isAlbum) { if (_isSingleMedia) { @@ -1123,6 +1131,13 @@ void SendFilesBox::pushBlock(int from, int till) { st::sendMediaPreviewSize, [=] { refreshAllAfterChanges(from); }); }, widget->lifetime()); + + block.orderUpdated() | rpl::start_with_next([=]{ + if (_priceTag) { + _priceTagBg = QImage(); + _priceTag->update(); + } + }, widget->lifetime()); } void SendFilesBox::refreshControls(bool initial) { diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index 7ba1a8070..8f3432ebd 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -161,6 +161,7 @@ private: [[nodiscard]] rpl::producer itemDeleteRequest() const; [[nodiscard]] rpl::producer itemReplaceRequest() const; [[nodiscard]] rpl::producer itemModifyRequest() const; + [[nodiscard]] rpl::producer<> orderUpdated() const; void setSendWay(Ui::SendFilesWay way); void toggleSpoilers(bool enabled); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp index 94d3b6286..b02409084 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp @@ -279,6 +279,7 @@ void AlbumPreview::finishDrag() { _finishDragAnimation.start([=] { update(); }, 0., 1., kDragDuration); updateSizeAnimated(layout); + _orderUpdated.fire({}); } else { for (const auto &thumb : _thumbs) { thumb->resetLayoutAnimation(); @@ -297,7 +298,7 @@ int AlbumPreview::countLayoutHeight( } void AlbumPreview::updateSizeAnimated( - const std::vector &layout) { + const std::vector &layout) { const auto newHeight = countLayoutHeight(layout); if (newHeight != _thumbsHeight) { _thumbsHeightAnimation.start( @@ -649,7 +650,7 @@ QImage AlbumPreview::generatePriceTagBackground() const { p.translate(geometry.center()); p.scale(wscale, hscale); p.translate(-geometry.center()); - thumb->paintInAlbum(p, 0, 0, 0., 0.); + thumb->paintInAlbum(p, 0, 0, 1., 1.); p.restore(); } p.end(); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h index 9a4b8bc05..9f1c9969d 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h @@ -51,6 +51,10 @@ public: return _thumbModified.events(); } + [[nodiscard]] rpl::producer<> orderUpdated() const { + return _orderUpdated.events(); + } + [[nodiscard]] QImage generatePriceTagBackground() const; protected: @@ -120,6 +124,7 @@ private: rpl::event_stream _thumbDeleted; rpl::event_stream _thumbChanged; rpl::event_stream _thumbModified; + rpl::event_stream<> _orderUpdated; base::unique_qptr _menu; From 54934fb8355e0645556ce58e5a473571e53a86b7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 29 Jun 2024 12:08:21 +0400 Subject: [PATCH 136/142] Fix problems with message effect preview. --- .../history_view_reactions_selector.cpp | 47 ++++++++++++++++--- .../history_view_reactions_selector.h | 3 +- Telegram/SourceFiles/menu/menu_send.cpp | 3 +- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index f884fe285..2fc888c6f 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/platform/ui_platform_utility.h" #include "ui/painter.h" +#include "history/view/media/history_view_sticker.h" #include "history/history.h" #include "history/history_item.h" #include "data/data_document.h" @@ -351,6 +352,16 @@ int Selector::countWidth(int desiredWidth, int maxWidth) { return std::max(2 * _skipx + _columns * _size, desiredWidth); } +int Selector::effectPreviewHeight() const { + if (_listMode != ChatHelpers::EmojiListMode::MessageEffects) { + return 0; + } + return st::previewMenu.shadow.extend.top() + + HistoryView::Sticker::MessageEffectSize().height() + + st::effectPreviewSend.height + + st::previewMenu.shadow.extend.bottom(); +} + QMargins Selector::marginsForShadow() const { const auto line = st::lineWidth; return useTransparency() @@ -409,9 +420,33 @@ void Selector::setOpaqueHeightExpand(int expand, Fn apply) { _opaqueApplyHeightExpand = std::move(apply); } -int Selector::minimalHeight() const { +int Selector::minimalHeight(int fullWidth) const { + auto inner = _recentRows * _size; + if (const auto stickers = int(_reactions.stickers.size())) { + // See StickersListWidget. + const auto listWidth = fullWidth + - marginsForShadow().left() + - marginsForShadow().right() + - _st.margin.left() + - _st.margin.right(); + const auto availableWidth = listWidth + - (st::stickerPanPadding - _st.margin.left()); + const auto min = st::stickerEffectWidthMin; + if (const auto columns = availableWidth / min) { + const auto rows = (stickers + columns - 1) / columns; + const auto singleWidth = availableWidth / columns; + const auto singleHeight = singleWidth; + const auto stickersHeight = rows * singleHeight; + inner += _st.header + stickersHeight; + } + } + if (_listMode == ChatHelpers::EmojiListMode::MessageEffects) { + inner += _st.searchMargin.top() + + _st.search.height + + _st.searchMargin.bottom(); + } return _skipy - + std::min(_recentRows * _size, st::emojiPanMinHeight) + + std::min(inner, st::emojiPanMinHeight) + st::emojiPanRadius + _st.padding.bottom(); } @@ -919,7 +954,7 @@ void Selector::expand() { const auto margins = marginsForShadow(); const auto heightLimit = _reactions.customAllowed ? st::emojiPanMaxHeight - : minimalHeight(); + : minimalHeight(width()); const auto opaqueAdded = _useTransparency ? 0 : _opaqueHeightExpand; const auto willBeHeight = std::min( parent.height() - y() + opaqueAdded, @@ -1170,9 +1205,9 @@ bool AdjustMenuGeometryForSelector( menu->setForceWidth(width - added); const auto height = menu->height(); const auto fullTop = margins.top() + categoriesAboutTop + extend.top(); - const auto minimalHeight = margins.top() - + selector->minimalHeight() - + margins.bottom(); + const auto minimalHeight = std::max( + margins.top() + selector->minimalHeight(width) + margins.bottom(), + selector->effectPreviewHeight()); const auto willBeHeightWithoutBottomPadding = fullTop + height - menu->st().shadow.extend.top(); diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h index 56f5210b7..107dbfdc7 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h @@ -102,11 +102,12 @@ public: [[nodiscard]] bool useTransparency() const; int countWidth(int desiredWidth, int maxWidth); + [[nodiscard]] int effectPreviewHeight() const; [[nodiscard]] QMargins marginsForShadow() const; [[nodiscard]] int extendTopForCategories() const; [[nodiscard]] int extendTopForCategoriesAndAbout(int width) const; [[nodiscard]] int opaqueExtendTopAbout(int width) const; - [[nodiscard]] int minimalHeight() const; + [[nodiscard]] int minimalHeight(int fullWidth) const; [[nodiscard]] int countAppearedWidth(float64 progress) const; void setSpecialExpandTopSkip(int skip); void initGeometry(int innerTop); diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index fb03be230..231e7ff23 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/event_filter.h" #include "base/unixtime.h" #include "boxes/abstract_box.h" +#include "boxes/premium_preview_box.h" #include "chat_helpers/compose/compose_show.h" #include "chat_helpers/stickers_emoji_pack.h" #include "core/shortcuts.h" @@ -526,7 +527,7 @@ void EffectPreview::setupSend(Details details) { if (const auto onstack = _close) { onstack(); } - Settings::ShowPremium(window, "message_effect"); + ShowPremiumPreviewBox(window, PremiumFeature::Effects); } return false; }); From dd57ad567f65bd9cbe25e488d79e913a101bc792 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 30 Jun 2024 10:15:49 +0400 Subject: [PATCH 137/142] Don't break by '/', '.' in the middle of the word. Fixes #27999. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index da1d618a7..ebd8609ee 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit da1d618a76a3b1bf652b110d6a1d7a03031d18b3 +Subproject commit ebd8609ee73d48186b905787dd5bb3bcbb4f9f3f From e8f16840dee035a07fbe898b6c0fdc63316e7a73 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 30 Jun 2024 10:26:35 +0400 Subject: [PATCH 138/142] Fix wrong search focusing. Fixes #28052. --- Telegram/SourceFiles/history/history_widget.cpp | 3 ++- Telegram/SourceFiles/history/history_widget.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 164909c20..fe0610daf 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -1855,12 +1855,13 @@ void HistoryWidget::setInnerFocus() { if (_list) { if (isSearching()) { _composeSearch->setInnerFocus(); - } else if (_chooseTheme && _chooseTheme->shouldBeShown()) { + } else if (isChoosingTheme()) { _chooseTheme->setFocus(); } else if (_showAnimation || _nonEmptySelection || (_list && _list->wasSelectedText()) || isRecording() + || isJoinChannel() || isBotStart() || isBlocked() || (!_canSendTexts && !_editMsgId)) { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 977415fba..b90ef037d 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -504,7 +504,7 @@ private: int countMembersDropdownHeightMax() const; void updateReplyToName(); - bool editingMessage() const { + [[nodiscard]] bool editingMessage() const { return _editMsgId != 0; } From 876c47c436edd2a28f661560e05394ee3702fe49 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 30 Jun 2024 20:10:50 +0400 Subject: [PATCH 139/142] Add 'section_separator_color' to webview palette. --- .../window/themes/window_theme.cpp | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp index 450cfe541..88bbe9cf2 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme.cpp @@ -1505,18 +1505,33 @@ bool ReadPaletteValues(const QByteArray &content, Fnc.getRgb(&r, &g, &b); + color.getRgb(&r, &g, &b); const auto hex = [](int component) { const auto digit = [](int c) { return QChar((c < 10) ? ('0' + c) : ('a' + c - 10)); }; return QString() + digit(component / 16) + digit(component % 16); }; - object.insert(name, '#' + hex(r) + hex(g) + hex(b)); + return '#' + hex(r) + hex(g) + hex(b); + }; + for (const auto &[name, color] : colors) { + object.insert(name, wrap(color->c)); + } + { + const auto bg = st::windowBg->c; + const auto shadow = st::shadowFg->c; + const auto shadowAlpha = shadow.alphaF(); + const auto mix = [&](int a, int b) { + return anim::interpolate(a, b, shadowAlpha); + }; + object.insert("section_separator_color", wrap(QColor( + mix(bg.red(), shadow.red()), + mix(bg.green(), shadow.green()), + mix(bg.blue(), shadow.blue())))); } return { .opaqueBg = st::windowBg->c, From 65f7bdb91411e5f03fb97df7b5f1f5b3adb01069 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 30 Jun 2024 21:13:25 +0400 Subject: [PATCH 140/142] Update API scheme on layer 183. --- .../inline_bots/bot_attach_web_view.cpp | 79 +++++++++++++++---- .../inline_bots/bot_attach_web_view.h | 4 + Telegram/SourceFiles/mtproto/scheme/api.tl | 12 +-- .../ui/chat/attach/attach_bot_webview.cpp | 3 +- .../ui/chat/attach/attach_bot_webview.h | 1 + 5 files changed, 75 insertions(+), 24 deletions(-) diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 68da29a3a..eb938ed26 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_blocked_peers.h" #include "api/api_common.h" +#include "base/qthelp_url.h" #include "core/click_handler_types.h" #include "data/data_bot_app.h" #include "data/data_changes.h" @@ -18,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document_media.h" #include "data/data_session.h" #include "data/data_web_page.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "main/main_domain.h" #include "storage/storage_domain.h" @@ -664,6 +666,19 @@ void AttachWebView::botHandleMenuButton(Ui::BotWebView::MenuButton button) { } } +bool AttachWebView::botValidateExternalLink(QString uri) { + const auto lower = uri.toLower(); + const auto allowed = _session->appConfig().get>( + "web_app_allowed_protocols", + std::vector{ u"http"_q, u"https"_q }); + for (const auto &protocol : allowed) { + if (lower.startsWith(protocol + u"://"_q)) { + return true; + } + } + return false; +} + void AttachWebView::botOpenIvLink(QString uri) { const auto window = _context ? _context->controller.get() : nullptr; if (window) { @@ -895,7 +910,7 @@ void AttachWebView::request(const WebViewButton &button) { _requestId = 0; const auto &data = result.data(); show( - data.vquery_id().v, + data.vquery_id().value_or_empty(), qs(data.vurl()), button.text, button.fromAttachMenu || button.url.isEmpty()); @@ -1208,25 +1223,61 @@ void AttachWebView::requestSimple(const WebViewButton &button) { MTP_string(button.startCommand), MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)), MTP_string("tdesktop") - )).done([=](const MTPSimpleWebViewResult &result) { + )).done([=](const MTPWebViewResult &result) { _requestId = 0; - result.match([&](const MTPDsimpleWebViewResultUrl &data) { - show( - uint64(), - qs(data.vurl()), - button.text, - false, - nullptr, - button.fromMainMenu); - }); + const auto &data = result.data(); + const auto queryId = uint64(); + show( + queryId, + qs(data.vurl()), + button.text, + false, + nullptr, + button.fromMainMenu); }).fail([=](const MTP::Error &error) { _requestId = 0; }).send(); } -void AttachWebView::requestMenu( +bool AttachWebView::openAppFromMenuLink( not_null controller, not_null bot) { + Expects(bot->botInfo != nullptr); + + const auto &url = bot->botInfo->botMenuButtonUrl; + const auto local = Core::TryConvertUrlToLocal(url); + const auto prefix = u"tg://resolve?"_q; + if (!local.startsWith(prefix)) { + return false; + } + const auto params = qthelp::url_parse_params( + local.mid(prefix.size()), + qthelp::UrlParamNameTransform::ToLower); + const auto domainParam = params.value(u"domain"_q); + const auto appnameParam = params.value(u"appname"_q); + const auto webChannelPreviewLink = (domainParam == u"s"_q) + && !appnameParam.isEmpty(); + const auto appname = webChannelPreviewLink ? QString() : appnameParam; + if (appname.isEmpty()) { + return false; + } + requestApp( + controller, + Api::SendAction(bot->owner().history(bot)), + bot, + appname, + params.value(u"startapp"_q), + true); + return true; +} + +void AttachWebView::requestMenu( + not_null controller, + not_null bot) { + if (openAppFromMenuLink(controller, bot)) { + return; + } + cancel(); _bot = bot; _context = std::make_unique(LookupContext( @@ -1257,7 +1308,7 @@ void AttachWebView::requestMenu( )).done([=](const MTPWebViewResult &result) { _requestId = 0; const auto &data = result.data(); - show(data.vquery_id().v, qs(data.vurl()), text); + show(data.vquery_id().value_or_empty(), qs(data.vurl()), text); }).fail([=](const MTP::Error &error) { _requestId = 0; if (error.type() == u"BOT_INVALID"_q) { @@ -1380,7 +1431,7 @@ void AttachWebView::requestAppView(bool allowWrite) { MTP_string(_startCommand), MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)), MTP_string("tdesktop") - )).done([=](const MTPAppWebViewResult &result) { + )).done([=](const MTPWebViewResult &result) { _requestId = 0; const auto &data = result.data(); const auto queryId = uint64(); diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index aebf99332..0f8cb6e13 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -165,6 +165,7 @@ private: bool botHandleLocalUri(QString uri, bool keepOpen) override; void botHandleInvoice(QString slug) override; void botHandleMenuButton(Ui::BotWebView::MenuButton button) override; + bool botValidateExternalLink(QString uri) override; void botOpenIvLink(QString uri) override; void botSendData(QByteArray data) override; void botSwitchInlineQuery( @@ -184,6 +185,9 @@ private: const std::unique_ptr &a, const Context &b); + bool openAppFromMenuLink( + not_null controller, + not_null bot); void requestWithOptionalConfirm( not_null bot, const WebViewButton &button, diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 91b310021..7b8700811 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -1424,9 +1424,7 @@ attachMenuBots#3c4301c0 hash:long bots:Vector users:Vector attachMenuBotsBot#93bf667f bot:AttachMenuBot users:Vector = AttachMenuBotsBot; -webViewResultUrl#c14557c query_id:long url:string = WebViewResult; - -simpleWebViewResultUrl#882f76bb url:string = SimpleWebViewResult; +webViewResultUrl#4d22ff98 flags:# fullsize:flags.1?true query_id:flags.0?long url:string = WebViewResult; webViewMessageSent#c94511c flags:# msg_id:flags.0?InputBotInlineMessageID = WebViewMessageSent; @@ -1556,8 +1554,6 @@ botApp#95fcd1d6 flags:# id:long access_hash:long short_name:string title:string messages.botApp#eb50adf5 flags:# inactive:flags.0?true request_write_access:flags.1?true has_settings:flags.2?true app:BotApp = messages.BotApp; -appWebViewResultUrl#3c1b4f0d url:string = AppWebViewResult; - inlineBotWebView#b57295d5 text:string url:string = InlineBotWebView; readParticipantDate#4a4ff172 user_id:long date:int = ReadParticipantDate; @@ -2172,9 +2168,9 @@ messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = mes messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots; messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot; messages.toggleBotInAttachMenu#69f59d69 flags:# write_allowed:flags.0?true bot:InputUser enabled:Bool = Bool; -messages.requestWebView#269dc2c1 flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON platform:string reply_to:flags.0?InputReplyTo send_as:flags.13?InputPeer = WebViewResult; +messages.requestWebView#269dc2c1 flags:# from_bot_menu:flags.4?true silent:flags.5?true compact:flags.7?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON platform:string reply_to:flags.0?InputReplyTo send_as:flags.13?InputPeer = WebViewResult; messages.prolongWebView#b0d81a83 flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to:flags.0?InputReplyTo send_as:flags.13?InputPeer = Bool; -messages.requestSimpleWebView#1a46500a flags:# from_switch_webview:flags.1?true from_side_menu:flags.2?true bot:InputUser url:flags.3?string start_param:flags.4?string theme_params:flags.0?DataJSON platform:string = SimpleWebViewResult; +messages.requestSimpleWebView#413a3e73 flags:# from_switch_webview:flags.1?true from_side_menu:flags.2?true compact:flags.7?true bot:InputUser url:flags.3?string start_param:flags.4?string theme_params:flags.0?DataJSON platform:string = WebViewResult; messages.sendWebViewResultMessage#a4314f5 bot_query_id:string result:InputBotInlineResult = WebViewMessageSent; messages.sendWebViewData#dc0242c8 bot:InputUser random_id:long button_text:string data:string = Updates; messages.transcribeAudio#269e9a49 peer:InputPeer msg_id:int = messages.TranscribedAudio; @@ -2196,7 +2192,7 @@ messages.getEmojiProfilePhotoGroups#21a548f3 hash:int = messages.EmojiGroups; messages.searchCustomEmoji#2c11c0d7 emoticon:string hash:long = EmojiList; messages.togglePeerTranslations#e47cb579 flags:# disabled:flags.0?true peer:InputPeer = Bool; messages.getBotApp#34fdc5c3 app:InputBotApp hash:long = messages.BotApp; -messages.requestAppWebView#8c5a3b3c flags:# write_allowed:flags.0?true peer:InputPeer app:InputBotApp start_param:flags.1?string theme_params:flags.2?DataJSON platform:string = AppWebViewResult; +messages.requestAppWebView#53618bce flags:# write_allowed:flags.0?true compact:flags.7?true peer:InputPeer app:InputBotApp start_param:flags.1?string theme_params:flags.2?DataJSON platform:string = WebViewResult; messages.setChatWallPaper#8ffacae1 flags:# for_both:flags.3?true revert:flags.4?true peer:InputPeer wallpaper:flags.0?InputWallPaper settings:flags.2?WallPaperSettings id:flags.1?int = Updates; messages.searchEmojiStickerSets#92b4494c flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets; messages.getSavedDialogs#5381d21a flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.SavedDialogs; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 3fbe6e484..5e9dde92e 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -803,8 +803,7 @@ void Panel::openExternalLink(const QJsonObject &args) { } const auto iv = args["try_instant_view"].toBool(); const auto url = args["url"].toString(); - const auto lower = url.toLower(); - if (!lower.startsWith("http://") && !lower.startsWith("https://")) { + if (!_delegate->botValidateExternalLink(url)) { LOG(("BotWebView Error: Bad url in openExternalLink: %1").arg(url)); _delegate->botClose(); return; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h index fb0231a01..91e423279 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h @@ -57,6 +57,7 @@ public: virtual bool botHandleLocalUri(QString uri, bool keepOpen) = 0; virtual void botHandleInvoice(QString slug) = 0; virtual void botHandleMenuButton(MenuButton button) = 0; + virtual bool botValidateExternalLink(QString uri) = 0; virtual void botOpenIvLink(QString uri) = 0; virtual void botSendData(QByteArray data) = 0; virtual void botSwitchInlineQuery( From 7eb98b50ec3d96b8c9119cd6feabf91404e1b66a Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 30 Jun 2024 21:21:51 +0400 Subject: [PATCH 141/142] Version 5.2. - Pay for content with Telegram Stars. - Enable local passcode unlock by Windows Hello and Touch ID. - Allow opening forums, topics and archive in a separate window. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 6 +++--- Telegram/build/version | 12 ++++++------ changelog.txt | 6 ++++++ 6 files changed, 24 insertions(+), 18 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 0fd7008c4..6358fd7a8 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.2.0.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 642b222da..8a7d5a43a 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,1,8,0 - PRODUCTVERSION 5,1,8,0 + FILEVERSION 5,2,0,0 + PRODUCTVERSION 5,2,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.1.8.0" + VALUE "FileVersion", "5.2.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.1.8.0" + VALUE "ProductVersion", "5.2.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 48faaa4b8..d66689ed4 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,1,8,0 - PRODUCTVERSION 5,1,8,0 + FILEVERSION 5,2,0,0 + PRODUCTVERSION 5,2,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "5.1.8.0" + VALUE "FileVersion", "5.2.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.1.8.0" + VALUE "ProductVersion", "5.2.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 751c3b026..2c230be0f 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 = 5001008; -constexpr auto AppVersionStr = "5.1.8"; -constexpr auto AppBetaVersion = true; +constexpr auto AppVersion = 5002000; +constexpr auto AppVersionStr = "5.2"; +constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 37dc61b49..fdada4a89 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5001008 -AppVersionStrMajor 5.1 -AppVersionStrSmall 5.1.8 -AppVersionStr 5.1.8 -BetaChannel 1 +AppVersion 5002000 +AppVersionStrMajor 5.2 +AppVersionStrSmall 5.2 +AppVersionStr 5.2.0 +BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 5.1.8.beta +AppVersionOriginal 5.2 diff --git a/changelog.txt b/changelog.txt index a5ea6efcc..ad1f99ab1 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +5.2 (30.06.24) + +- Pay for content with Telegram Stars. +- Enable local passcode unlock by Windows Hello and Touch ID. +- Allow opening forums, topics and archive in a separate window. + 5.1.8 beta (15.06.24) - Support nice blockquotes and code blocks edition when composing messages. From 707951accbd8aa8d698433de9ce5cedbbbe39b9e Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 30 Jun 2024 23:59:03 +0400 Subject: [PATCH 142/142] Version 5.2: Fix build with GCC. --- Telegram/SourceFiles/api/api_credits.cpp | 10 ---------- .../SourceFiles/boxes/peers/edit_peer_info_box.cpp | 1 - Telegram/SourceFiles/boxes/send_files_box.cpp | 2 -- Telegram/SourceFiles/data/data_media_types.cpp | 1 - .../info/bot/earn/info_earn_inner_widget.cpp | 3 +-- .../channel_statistics/earn/info_earn_inner_widget.cpp | 4 ---- Telegram/SourceFiles/window/main_window.cpp | 1 - Telegram/SourceFiles/window/window_controller.cpp | 1 - 8 files changed, 1 insertion(+), 22 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index fb0a4e9c6..356e2cffa 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -69,16 +69,6 @@ constexpr auto kTransactionsLimit = 100; }, [](const auto &) { return PeerId(0); }).value; - const auto isBot = [&] { - if (barePeerId) { - if (const auto p = peer->owner().peer(PeerId(barePeerId))) { - if (const auto u = p->asUser()) { - return u->isBot(); - } - } - } - return false; - }(); return Data::CreditsHistoryEntry{ .id = qs(tl.data().vid()), .title = qs(tl.data().vtitle().value_or_empty()), diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 86588f203..5623bea94 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -1586,7 +1586,6 @@ void Controller::fillBotBalanceButton() { const auto icon = Ui::CreateChild(button); icon->resize(Size(st::menuIconLinks.width() - kSizeShift)); - const auto bg = st::boxBg->c; auto colorized = [&] { auto f = QFile(Ui::Premium::Svg()); if (!f.open(QIODevice::ReadOnly)) { diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 809b6f09f..0eade94ca 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -116,7 +116,6 @@ void EditPriceBox( not_null session, uint64 price, Fn apply) { - const auto owner = &session->data(); box->setTitle(tr::lng_paid_title()); AddSubsectionTitle( box->verticalLayout(), @@ -837,7 +836,6 @@ QImage SendFilesBox::preparePriceTagBg(QSize size) const { bg = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied); bg.fill(Qt::black); } - const auto bgSize = bg.size() / bg.devicePixelRatio(); auto result = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(ratio); diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 72ca05542..15dfedecd 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -2008,7 +2008,6 @@ ItemPreview MediaInvoice::toPreview(ToPreviewOptions options) const { return Media::toPreview(options); } auto counts = AlbumCounts(); - const auto item = parent(); auto images = std::vector(); auto context = std::vector(); const auto existing = options.existing; diff --git a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp index bc6f90e1d..2b152ad6f 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_earn_inner_widget.cpp @@ -119,7 +119,6 @@ void InnerWidget::fill() { const auto container = this; const auto &data = _state; const auto multiplier = data.usdRate * Data::kEarnMultiplier; - const auto session = &_peer->session(); auto availableBalanceValue = rpl::single( data.availableBalance @@ -192,7 +191,7 @@ void InnerWidget::fill() { Ui::ToggleChildrenVisibility(line, true); Ui::AddSkip(container); - const auto sub = container->add( + container->add( object_ptr( container, text(), diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp index 82e020d68..9fea4508c 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp @@ -281,10 +281,6 @@ void InnerWidget::load() { rpl::lifetime apiPremiumBotLifetime; }; const auto state = lifetime().make_state(_peer); - const auto api = lifetime().make_state( - _peer->asChannel()); - const auto apiCredits = lifetime().make_state( - _peer); Info::Statistics::FillLoading( this, diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index e50ffd737..3f7da1aa3 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -612,7 +612,6 @@ WindowPosition MainWindow::nextInitialChildPosition(SeparateId childId) { const auto adjust = [&](int value) { return (value * 3 / 4); }; - const auto secondaryWithChatsList = !childId.primary() && childId.hasChatsList(); const auto width = OptionNewWindowsSizeAsFirst.value() ? Core::App().settings().windowPosition().w : childId.primary() diff --git a/Telegram/SourceFiles/window/window_controller.cpp b/Telegram/SourceFiles/window/window_controller.cpp index 69bca734f..8ef614d21 100644 --- a/Telegram/SourceFiles/window/window_controller.cpp +++ b/Telegram/SourceFiles/window/window_controller.cpp @@ -143,7 +143,6 @@ void Controller::showAccount( MsgId singlePeerShowAtMsgId) { Expects(isPrimary() || _id.account == account); - const auto prevAccount = _id.account; const auto prevSession = maybeSession(); const auto prevSessionUniqueId = prevSession ? prevSession->uniqueId()