Moved out special widget for credits input to single place.

This commit is contained in:
23rd 2024-06-24 02:15:15 +03:00 committed by John Preston
parent 93aebc747d
commit 37181f9d0a
3 changed files with 304 additions and 272 deletions

View file

@ -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<Main::Session*> session) {
const auto key = u"stars_revenue_withdrawal_min"_q;
return session->appConfig().get<int>(key, 1000);
}
void AddHeader(
not_null<Ui::VerticalLayout*> 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<Ui::CenterWrap<Ui::RpWidget>>(
container,
object_ptr<Ui::RpWidget>(container)))->entity();
const auto majorLabel = Ui::CreateChild<Ui::FlatLabel>(
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<Ui::CenterWrap<>>(
container,
object_ptr<Ui::FlatLabel>(
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<Ui::RoundButton>(
container,
rpl::never<QString>(),
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<Ui::FlatLabel>(
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<Ui::FlatLabel>(
button,
tr::lng_bot_earn_balance_button_locked(),
st::botEarnLockedButtonLabel);
lockedLabelTop->setTextColorOverride(lockedColor);
lockedLabelTop->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto lockedLabelBottom = Ui::CreateChild<Ui::FlatLabel>(
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<rpl::lifetime>();
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<Ui::DividerLabel>(
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();

View file

@ -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<Main::Session*> session) {
const auto key = u"stars_revenue_withdrawal_min"_q;
return session->appConfig().get<int>(key, 1000);
}
class Balance final
: public Ui::RpWidget
, public Ui::AbstractTooltipShower {
@ -643,4 +658,272 @@ void SmallBalanceBox(
}
}
void AddWithdrawalWidget(
not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
rpl::producer<uint64> availableBalanceValue,
rpl::producer<QDateTime> dateValue,
rpl::producer<bool> lockedValue,
rpl::producer<QString> usdValue) {
Ui::AddSkip(container);
const auto labels = container->add(
object_ptr<Ui::CenterWrap<Ui::RpWidget>>(
container,
object_ptr<Ui::RpWidget>(container)))->entity();
const auto majorLabel = Ui::CreateChild<Ui::FlatLabel>(
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<Ui::CenterWrap<>>(
container,
object_ptr<Ui::FlatLabel>(
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<Ui::RoundButton>(
container,
rpl::never<QString>(),
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<Ui::FlatLabel>(
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<uint64>;
const auto currentBalance = input->lifetime().make_state<Balance>(
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<Ui::FlatLabel>(
button,
tr::lng_bot_earn_balance_button_locked(),
st::botEarnLockedButtonLabel);
lockedLabelTop->setTextColorOverride(lockedColor);
lockedLabelTop->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto lockedLabelBottom = Ui::CreateChild<Ui::FlatLabel>(
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<rpl::lifetime>();
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<Ui::DividerLabel>(
container,
std::move(about),
st::defaultBoxDividerLabelPadding,
RectPart::Top | RectPart::Bottom));
Ui::AddSkip(container);
}
} // namespace Settings

View file

@ -39,6 +39,15 @@ void FillCreditOptions(
rpl::producer<uint64> balanceValue,
bool rightAlign);
void AddWithdrawalWidget(
not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
rpl::producer<uint64> availableBalanceValue,
rpl::producer<QDateTime> dateValue,
rpl::producer<bool> lockedValue,
rpl::producer<QString> usdValue);
void ReceiptCreditsBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,