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,