mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-08-23 02:37:11 +00:00
Allow settings gift price in TON.
This commit is contained in:
parent
985324ac12
commit
aa499eab61
@ -3559,6 +3559,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
"lng_gift_send_button_self" = "Buy a Gift for {cost}";
|
"lng_gift_send_button_self" = "Buy a Gift for {cost}";
|
||||||
"lng_gift_buy_resale_title" = "Buy {name}";
|
"lng_gift_buy_resale_title" = "Buy {name}";
|
||||||
"lng_gift_buy_resale_button" = "Buy for {cost}";
|
"lng_gift_buy_resale_button" = "Buy for {cost}";
|
||||||
|
"lng_gift_buy_resale_equals" = "Equals to {cost}";
|
||||||
|
"lng_gift_buy_resale_only_ton" = "The seller only accepts TON as payment";
|
||||||
|
"lng_gift_buy_resale_pay_stars" = "Pay in Stars";
|
||||||
|
"lng_gift_buy_resale_pay_ton" = "Pay in TON";
|
||||||
"lng_gift_buy_resale_confirm" = "Do you want to buy {name} for {price} and gift it to {user}?";
|
"lng_gift_buy_resale_confirm" = "Do you want to buy {name} for {price} and gift it to {user}?";
|
||||||
"lng_gift_buy_resale_confirm_self" = "Do you want to buy {name} for {price}?";
|
"lng_gift_buy_resale_confirm_self" = "Do you want to buy {name} for {price}?";
|
||||||
"lng_gift_buy_price_change_title" = "Price change!";
|
"lng_gift_buy_price_change_title" = "Price change!";
|
||||||
@ -3704,12 +3708,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
"lng_gift_sell_unlist_title" = "Unlist {name}";
|
"lng_gift_sell_unlist_title" = "Unlist {name}";
|
||||||
"lng_gift_sell_unlist_sure" = "Are you sure you want to unlist your gift?";
|
"lng_gift_sell_unlist_sure" = "Are you sure you want to unlist your gift?";
|
||||||
"lng_gift_sell_title" = "Price in Stars";
|
"lng_gift_sell_title" = "Price in Stars";
|
||||||
"lng_gift_sell_placeholder" = "Enter price in Stars";
|
|
||||||
"lng_gift_sell_about" = "You will receive {percent} of the selected amount.";
|
"lng_gift_sell_about" = "You will receive {percent} of the selected amount.";
|
||||||
"lng_gift_sell_amount#one" = "You will receive **{count}** Star.";
|
"lng_gift_sell_amount#one" = "You will receive **{count}** Star.";
|
||||||
"lng_gift_sell_amount#other" = "You will receive **{count}** Stars.";
|
"lng_gift_sell_amount#other" = "You will receive **{count}** Stars.";
|
||||||
"lng_gift_sell_min_price#one" = "Minimum price is {count} Star.";
|
"lng_gift_sell_min_price#one" = "Minimum price is {count} Star.";
|
||||||
"lng_gift_sell_min_price#other" = "Minimum price is {count} Stars.";
|
"lng_gift_sell_min_price#other" = "Minimum price is {count} Stars.";
|
||||||
|
"lng_gift_sell_only_ton" = "Only Accept TON";
|
||||||
|
"lng_gift_sell_only_ton_about" = "If the buyer pays you in TON, there's no risk of refunds, unlike Stars payments.";
|
||||||
|
"lng_gift_sell_amount_ton#one" = "You will receive **{count}** TON.";
|
||||||
|
"lng_gift_sell_amount_ton#other" = "You will receive **{count}** TON.";
|
||||||
|
"lng_gift_sell_min_price_ton#one" = "Minimum price is {count} TON.";
|
||||||
|
"lng_gift_sell_min_price_ton#other" = "Minimum price is {count} TON.";
|
||||||
|
"lng_gift_sell_title_ton" = "Price in TON";
|
||||||
"lng_gift_sell_put" = "Put for Sale";
|
"lng_gift_sell_put" = "Put for Sale";
|
||||||
"lng_gift_sell_update" = "Update the Price";
|
"lng_gift_sell_update" = "Update the Price";
|
||||||
"lng_gift_sell_toast" = "{name} is now for sale!";
|
"lng_gift_sell_toast" = "{name} is now for sale!";
|
||||||
|
@ -895,9 +895,10 @@ std::optional<Data::StarGift> FromTL(
|
|||||||
? peerFromMTP(*data.vowner_id())
|
? peerFromMTP(*data.vowner_id())
|
||||||
: PeerId()),
|
: PeerId()),
|
||||||
.releasedBy = releasedBy,
|
.releasedBy = releasedBy,
|
||||||
.number = data.vnum().v,
|
.nanoTonForResale = FindTonForResale(data.vresell_amount()),
|
||||||
.starsForResale = FindStarsForResale(data.vresell_amount()),
|
.starsForResale = FindStarsForResale(data.vresell_amount()),
|
||||||
.tonForResale = FindTonForResale(data.vresell_amount()),
|
.number = data.vnum().v,
|
||||||
|
.onlyAcceptTon = data.is_resale_ton_only(),
|
||||||
.model = *model,
|
.model = *model,
|
||||||
.pattern = *pattern,
|
.pattern = *pattern,
|
||||||
}),
|
}),
|
||||||
|
@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "data/stickers/data_custom_emoji.h"
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
#include "history/admin_log/history_admin_log_item.h"
|
#include "history/admin_log/history_admin_log_item.h"
|
||||||
|
#include "history/view/controls/history_view_suggest_options.h"
|
||||||
#include "history/view/media/history_view_media_generic.h"
|
#include "history/view/media/history_view_media_generic.h"
|
||||||
#include "history/view/media/history_view_unique_gift.h"
|
#include "history/view/media/history_view_unique_gift.h"
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
@ -71,6 +72,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "ui/chat/chat_style.h"
|
#include "ui/chat/chat_style.h"
|
||||||
#include "ui/chat/chat_theme.h"
|
#include "ui/chat/chat_theme.h"
|
||||||
#include "ui/controls/emoji_button.h"
|
#include "ui/controls/emoji_button.h"
|
||||||
|
#include "ui/controls/ton_common.h"
|
||||||
#include "ui/controls/userpic_button.h"
|
#include "ui/controls/userpic_button.h"
|
||||||
#include "ui/effects/path_shift_gradient.h"
|
#include "ui/effects/path_shift_gradient.h"
|
||||||
#include "ui/effects/premium_graphics.h"
|
#include "ui/effects/premium_graphics.h"
|
||||||
@ -233,6 +235,37 @@ struct SessionResalePrices {
|
|||||||
crl::time lastReceived = 0;
|
crl::time lastReceived = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] CreditsAmount StarsFromTon(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
CreditsAmount ton) {
|
||||||
|
const auto appConfig = &session->appConfig();
|
||||||
|
const auto starsRate = appConfig->starsWithdrawRate() / 100.;
|
||||||
|
const auto tonRate = appConfig->currencyWithdrawRate();
|
||||||
|
if (!starsRate) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto count = (ton.value() * tonRate) / starsRate;
|
||||||
|
return CreditsAmount(int(base::SafeRound(count)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] CreditsAmount TonFromStars(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
CreditsAmount stars) {
|
||||||
|
const auto appConfig = &session->appConfig();
|
||||||
|
const auto starsRate = appConfig->starsWithdrawRate() / 100.;
|
||||||
|
const auto tonRate = appConfig->currencyWithdrawRate();
|
||||||
|
if (!tonRate) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto count = (stars.value() * starsRate) / tonRate;
|
||||||
|
const auto whole = int(std::floor(count));
|
||||||
|
const auto cents = int(base::SafeRound((count - whole) * 100));
|
||||||
|
return CreditsAmount(
|
||||||
|
whole,
|
||||||
|
cents * (Ui::kNanosInOne / 100),
|
||||||
|
CreditsType::Ton);
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] not_null<SessionResalePrices*> ResalePrices(
|
[[nodiscard]] not_null<SessionResalePrices*> ResalePrices(
|
||||||
not_null<Main::Session*> session) {
|
not_null<Main::Session*> session) {
|
||||||
static auto result = base::flat_map<
|
static auto result = base::flat_map<
|
||||||
@ -4403,19 +4436,6 @@ void ShowUniqueGiftWearBox(
|
|||||||
session,
|
session,
|
||||||
st::creditsBoxButtonLabel,
|
st::creditsBoxButtonLabel,
|
||||||
&st::giftBox.button.textFg);
|
&st::giftBox.button.textFg);
|
||||||
|
|
||||||
rpl::combine(
|
|
||||||
box->widthValue(),
|
|
||||||
button->widthValue()
|
|
||||||
) | rpl::start_with_next([=](int outer, int inner) {
|
|
||||||
const auto padding = st::giftBox.buttonPadding;
|
|
||||||
const auto wanted = outer - padding.left() - padding.right();
|
|
||||||
if (inner != wanted) {
|
|
||||||
button->resizeToWidth(wanted);
|
|
||||||
button->moveToLeft(padding.left(), padding.top());
|
|
||||||
}
|
|
||||||
}, box->lifetime());
|
|
||||||
|
|
||||||
AddUniqueCloseButton(box, {});
|
AddUniqueCloseButton(box, {});
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -4473,12 +4493,14 @@ void UpdateGiftSellPrice(
|
|||||||
std::shared_ptr<ChatHelpers::Show> show,
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
std::shared_ptr<Data::UniqueGift> unique,
|
std::shared_ptr<Data::UniqueGift> unique,
|
||||||
Data::SavedStarGiftId savedId,
|
Data::SavedStarGiftId savedId,
|
||||||
int price) {
|
CreditsAmount price) {
|
||||||
const auto was = unique->starsForResale;
|
const auto was = unique->starsForResale;
|
||||||
const auto session = &show->session();
|
const auto session = &show->session();
|
||||||
session->api().request(MTPpayments_UpdateStarGiftPrice(
|
session->api().request(MTPpayments_UpdateStarGiftPrice(
|
||||||
Api::InputSavedStarGiftId(savedId, unique),
|
Api::InputSavedStarGiftId(savedId, unique),
|
||||||
MTP_starsAmount(MTP_long(price), MTP_int(0))
|
(price
|
||||||
|
? StarsAmountToTL(price)
|
||||||
|
: MTP_starsAmount(MTP_long(0), MTP_int(0)))
|
||||||
)).done([=](const MTPUpdates &result) {
|
)).done([=](const MTPUpdates &result) {
|
||||||
session->api().applyUpdates(result);
|
session->api().applyUpdates(result);
|
||||||
show->showToast((!price
|
show->showToast((!price
|
||||||
@ -4489,8 +4511,26 @@ void UpdateGiftSellPrice(
|
|||||||
tr::now,
|
tr::now,
|
||||||
lt_name,
|
lt_name,
|
||||||
Data::UniqueGiftName(*unique)));
|
Data::UniqueGiftName(*unique)));
|
||||||
|
const auto setStars = [&](CreditsAmount amount) {
|
||||||
unique->starsForResale = price;
|
unique->starsForResale = amount.whole();
|
||||||
|
};
|
||||||
|
const auto setTon = [&](CreditsAmount amount) {
|
||||||
|
unique->nanoTonForResale = amount.whole() * Ui::kNanosInOne
|
||||||
|
+ amount.nano();
|
||||||
|
};
|
||||||
|
if (!price) {
|
||||||
|
setStars({});
|
||||||
|
setTon({});
|
||||||
|
unique->onlyAcceptTon = false;
|
||||||
|
} else if (price.ton()) {
|
||||||
|
setStars(StarsFromTon(session, price));
|
||||||
|
setTon(price);
|
||||||
|
unique->onlyAcceptTon = true;
|
||||||
|
} else {
|
||||||
|
setStars(price);
|
||||||
|
setTon(TonFromStars(session, price));
|
||||||
|
unique->onlyAcceptTon = false;
|
||||||
|
}
|
||||||
session->data().notifyGiftUpdate({
|
session->data().notifyGiftUpdate({
|
||||||
.id = savedId,
|
.id = savedId,
|
||||||
.slug = unique->slug,
|
.slug = unique->slug,
|
||||||
@ -4517,80 +4557,103 @@ void UniqueGiftSellBox(
|
|||||||
Data::SavedStarGiftId savedId,
|
Data::SavedStarGiftId savedId,
|
||||||
int price,
|
int price,
|
||||||
Settings::GiftWearBoxStyleOverride st) {
|
Settings::GiftWearBoxStyleOverride st) {
|
||||||
box->setTitle(tr::lng_gift_sell_title());
|
const auto session = &show->session();
|
||||||
|
const auto &appConfig = session->appConfig();
|
||||||
|
const auto starsMin = appConfig.giftResaleStarsMin();
|
||||||
|
const auto nanoTonMin = appConfig.giftResaleNanoTonMin();
|
||||||
|
const auto starsThousandths = appConfig.giftResaleStarsThousandths();
|
||||||
|
const auto nanoTonThousandths = appConfig.giftResaleNanoTonThousandths();
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
rpl::variable<bool> onlyTon;
|
||||||
|
rpl::variable<CreditsAmount> price;
|
||||||
|
Fn<std::optional<CreditsAmount>()> computePrice;
|
||||||
|
rpl::event_stream<> errors;
|
||||||
|
};
|
||||||
|
const auto state = box->lifetime().make_state<State>();
|
||||||
|
state->onlyTon = unique->onlyAcceptTon;
|
||||||
|
const auto priceNow = unique->onlyAcceptTon
|
||||||
|
? CreditsAmount(
|
||||||
|
unique->nanoTonForResale / Ui::kNanosInOne,
|
||||||
|
unique->nanoTonForResale % Ui::kNanosInOne,
|
||||||
|
CreditsType::Ton)
|
||||||
|
: CreditsAmount(unique->starsForResale);
|
||||||
|
state->price = priceNow
|
||||||
|
? priceNow
|
||||||
|
: price
|
||||||
|
? CreditsAmount(price)
|
||||||
|
: CreditsAmount(starsMin);
|
||||||
|
|
||||||
|
box->setTitle(rpl::conditional(
|
||||||
|
state->onlyTon.value(),
|
||||||
|
tr::lng_gift_sell_title_ton(),
|
||||||
|
tr::lng_gift_sell_title()));
|
||||||
box->setStyle(st.box ? *st.box : st::upgradeGiftBox);
|
box->setStyle(st.box ? *st.box : st::upgradeGiftBox);
|
||||||
box->setWidth(st::boxWideWidth);
|
box->setWidth(st::boxWideWidth);
|
||||||
|
|
||||||
box->addTopButton(st.close ? *st.close : st::boxTitleClose, [=] {
|
box->addTopButton(st.close ? *st.close : st::boxTitleClose, [=] {
|
||||||
box->closeBox();
|
box->closeBox();
|
||||||
});
|
});
|
||||||
const auto priceNow = unique->starsForResale;
|
|
||||||
const auto name = Data::UniqueGiftName(*unique);
|
const auto name = Data::UniqueGiftName(*unique);
|
||||||
const auto slug = unique->slug;
|
const auto slug = unique->slug;
|
||||||
|
|
||||||
const auto session = &show->session();
|
const auto container = box->verticalLayout();
|
||||||
AddSubsectionTitle(
|
auto priceInput = HistoryView::AddStarsTonPriceInput(container, {
|
||||||
box->verticalLayout(),
|
.session = session,
|
||||||
tr::lng_gift_sell_placeholder(),
|
.showTon = state->onlyTon.value(),
|
||||||
(st::boxRowPadding - QMargins(
|
.price = state->price.current(),
|
||||||
st::defaultSubsectionTitlePadding.left(),
|
.starsMin = starsMin,
|
||||||
0,
|
.starsMax = appConfig.giftResaleStarsMax(),
|
||||||
st::defaultSubsectionTitlePadding.right(),
|
.nanoTonMin = nanoTonMin,
|
||||||
st::defaultSubsectionTitlePadding.bottom())));
|
.nanoTonMax = appConfig.giftResaleNanoTonMax(),
|
||||||
const auto &appConfig = session->appConfig();
|
|
||||||
const auto limit = appConfig.giftResalePriceMax();
|
|
||||||
const auto minimal = appConfig.giftResalePriceMin();
|
|
||||||
const auto thousandths = appConfig.giftResaleReceiveThousandths();
|
|
||||||
const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
|
||||||
box,
|
|
||||||
st::editTagField.heightMin));
|
|
||||||
auto owned = object_ptr<Ui::NumberInput>(
|
|
||||||
wrap,
|
|
||||||
st::editTagField,
|
|
||||||
rpl::single(QString()),
|
|
||||||
QString::number(priceNow ? priceNow : price ? price : minimal),
|
|
||||||
limit);
|
|
||||||
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->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
|
||||||
auto p = QPainter(field);
|
|
||||||
st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width());
|
|
||||||
}, field->lifetime());
|
|
||||||
field->selectAll();
|
|
||||||
box->setFocusCallback([=] {
|
|
||||||
field->setFocusFast();
|
|
||||||
});
|
});
|
||||||
|
state->price = std::move(priceInput.result);
|
||||||
|
state->computePrice = std::move(priceInput.computeResult);
|
||||||
|
box->setFocusCallback(std::move(priceInput.focusCallback));
|
||||||
|
|
||||||
const auto errors = box->lifetime().make_state<
|
|
||||||
rpl::event_stream<>
|
|
||||||
>();
|
|
||||||
auto goods = rpl::merge(
|
auto goods = rpl::merge(
|
||||||
rpl::single(rpl::empty) | rpl::map_to(true),
|
rpl::single(rpl::empty) | rpl::map_to(true),
|
||||||
base::qt_signal_producer(
|
std::move(priceInput.updates) | rpl::map_to(true),
|
||||||
field,
|
state->errors.events() | rpl::map_to(false)
|
||||||
&Ui::NumberInput::changed
|
|
||||||
) | rpl::map_to(true),
|
|
||||||
errors->events() | rpl::map_to(false)
|
|
||||||
) | rpl::start_spawning(box->lifetime());
|
) | rpl::start_spawning(box->lifetime());
|
||||||
auto text = rpl::duplicate(goods) | rpl::map([=](bool good) {
|
auto text = rpl::duplicate(goods) | rpl::map([=](bool good) {
|
||||||
const auto value = field->getLastText().toInt();
|
const auto value = state->computePrice();
|
||||||
const auto receive = (int64(value) * thousandths) / 1000;
|
const auto amount = value ? value->value() : 0.;
|
||||||
return !good
|
const auto tonMin = nanoTonMin / float64(Ui::kNanosInOne);
|
||||||
? tr::lng_gift_sell_min_price(
|
const auto enough = value
|
||||||
|
&& (amount >= (value->ton() ? tonMin : starsMin));
|
||||||
|
const auto receive = !value
|
||||||
|
? 0
|
||||||
|
: value->ton()
|
||||||
|
? ((amount * nanoTonThousandths) / 1000.)
|
||||||
|
: ((int64(amount) * starsThousandths) / 1000);
|
||||||
|
const auto thousandths = state->onlyTon.current()
|
||||||
|
? nanoTonThousandths
|
||||||
|
: starsThousandths;
|
||||||
|
return (!good || !value)
|
||||||
|
? (state->onlyTon.current()
|
||||||
|
? tr::lng_gift_sell_min_price_ton(
|
||||||
tr::now,
|
tr::now,
|
||||||
lt_count,
|
lt_count,
|
||||||
minimal,
|
nanoTonMin / float64(Ui::kNanosInOne),
|
||||||
Ui::Text::RichLangValue)
|
Ui::Text::RichLangValue)
|
||||||
: (value >= minimal)
|
: tr::lng_gift_sell_min_price(
|
||||||
? tr::lng_gift_sell_amount(
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
starsMin,
|
||||||
|
Ui::Text::RichLangValue))
|
||||||
|
: enough
|
||||||
|
? (value->ton()
|
||||||
|
? tr::lng_gift_sell_amount_ton(
|
||||||
tr::now,
|
tr::now,
|
||||||
lt_count,
|
lt_count,
|
||||||
receive,
|
receive,
|
||||||
Ui::Text::RichLangValue)
|
Ui::Text::RichLangValue)
|
||||||
|
: tr::lng_gift_sell_amount(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
receive,
|
||||||
|
Ui::Text::RichLangValue))
|
||||||
: tr::lng_gift_sell_about(
|
: tr::lng_gift_sell_about(
|
||||||
tr::now,
|
tr::now,
|
||||||
lt_percent,
|
lt_percent,
|
||||||
@ -4603,37 +4666,50 @@ void UniqueGiftSellBox(
|
|||||||
box->verticalLayout()->resizeToWidth(box->width());
|
box->verticalLayout()->resizeToWidth(box->width());
|
||||||
}),
|
}),
|
||||||
st::boxLabel));
|
st::boxLabel));
|
||||||
Ui::AddSkip(box->verticalLayout());
|
|
||||||
|
Ui::AddSkip(container);
|
||||||
|
Ui::AddSkip(container);
|
||||||
|
box->addRow(object_ptr<Ui::PlainShadow>(box));
|
||||||
|
Ui::AddSkip(container);
|
||||||
|
Ui::AddSkip(container);
|
||||||
|
|
||||||
|
const auto onlyTon = box->addRow(
|
||||||
|
object_ptr<Ui::Checkbox>(
|
||||||
|
box,
|
||||||
|
tr::lng_gift_sell_only_ton(tr::now),
|
||||||
|
state->onlyTon.current(),
|
||||||
|
st::defaultCheckbox));
|
||||||
|
state->onlyTon = onlyTon->checkedValue();
|
||||||
|
|
||||||
|
Ui::AddSkip(container);
|
||||||
|
box->addRow(
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
container,
|
||||||
|
tr::lng_gift_sell_only_ton_about(Ui::Text::RichLangValue),
|
||||||
|
st::boxDividerLabel));
|
||||||
|
Ui::AddSkip(container);
|
||||||
|
|
||||||
rpl::duplicate(goods) | rpl::start_with_next([=](bool good) {
|
rpl::duplicate(goods) | rpl::start_with_next([=](bool good) {
|
||||||
details->setTextColorOverride(
|
details->setTextColorOverride(
|
||||||
good ? st::windowSubTextFg->c : st::boxTextFgError->c);
|
good ? st::windowSubTextFg->c : st::boxTextFgError->c);
|
||||||
}, details->lifetime());
|
}, details->lifetime());
|
||||||
|
|
||||||
QObject::connect(field, &NumberInput::submitted, [=] {
|
const auto submit = [=] {
|
||||||
const auto count = field->getLastText().toInt();
|
const auto value = state->computePrice();
|
||||||
if (count < minimal) {
|
if (!value) {
|
||||||
field->showError();
|
state->errors.fire({});
|
||||||
errors->fire({});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
box->closeBox();
|
box->closeBox();
|
||||||
UpdateGiftSellPrice(show, unique, savedId, count);
|
UpdateGiftSellPrice(show, unique, savedId, *value);
|
||||||
});
|
};
|
||||||
const auto button = box->addButton(priceNow
|
std::move(
|
||||||
|
priceInput.submits
|
||||||
|
) | rpl::start_with_next(submit, details->lifetime());
|
||||||
|
auto submitText = priceNow
|
||||||
? tr::lng_gift_sell_update()
|
? tr::lng_gift_sell_update()
|
||||||
: tr::lng_gift_sell_put(), [=] { field->submitted({}); });
|
: tr::lng_gift_sell_put();
|
||||||
rpl::combine(
|
box->addButton(std::move(submitText), submit);
|
||||||
box->widthValue(),
|
|
||||||
button->widthValue()
|
|
||||||
) | rpl::start_with_next([=](int outer, int inner) {
|
|
||||||
const auto padding = st::giftBox.buttonPadding;
|
|
||||||
const auto wanted = outer - padding.left() - padding.right();
|
|
||||||
if (inner != wanted) {
|
|
||||||
button->resizeToWidth(wanted);
|
|
||||||
button->moveToLeft(padding.left(), padding.top());
|
|
||||||
}
|
|
||||||
}, box->lifetime());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShowUniqueGiftSellBox(
|
void ShowUniqueGiftSellBox(
|
||||||
@ -4874,17 +4950,6 @@ void UpgradeBox(
|
|||||||
st::creditsBoxButtonLabel,
|
st::creditsBoxButtonLabel,
|
||||||
&st::giftBox.button.textFg);
|
&st::giftBox.button.textFg);
|
||||||
}
|
}
|
||||||
rpl::combine(
|
|
||||||
box->widthValue(),
|
|
||||||
button->widthValue()
|
|
||||||
) | rpl::start_with_next([=](int outer, int inner) {
|
|
||||||
const auto padding = st::giftBox.buttonPadding;
|
|
||||||
const auto wanted = outer - padding.left() - padding.right();
|
|
||||||
if (inner != wanted) {
|
|
||||||
button->resizeToWidth(wanted);
|
|
||||||
button->moveToLeft(padding.left(), padding.top());
|
|
||||||
}
|
|
||||||
}, box->lifetime());
|
|
||||||
|
|
||||||
AddUniqueCloseButton(box, {});
|
AddUniqueCloseButton(box, {});
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ void UpdateGiftSellPrice(
|
|||||||
std::shared_ptr<ChatHelpers::Show> show,
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
std::shared_ptr<Data::UniqueGift> unique,
|
std::shared_ptr<Data::UniqueGift> unique,
|
||||||
Data::SavedStarGiftId savedId,
|
Data::SavedStarGiftId savedId,
|
||||||
int price);
|
CreditsAmount price);
|
||||||
void ShowUniqueGiftSellBox(
|
void ShowUniqueGiftSellBox(
|
||||||
std::shared_ptr<ChatHelpers::Show> show,
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
std::shared_ptr<Data::UniqueGift> unique,
|
std::shared_ptr<Data::UniqueGift> unique,
|
||||||
|
@ -1533,6 +1533,18 @@ bool ResolveStarsSettings(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ResolveTonSettings(
|
||||||
|
Window::SessionController *controller,
|
||||||
|
const Match &match,
|
||||||
|
const QVariant &context) {
|
||||||
|
if (!controller) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
controller->showSettings(::Settings::CurrencyId());
|
||||||
|
controller->window().activate();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
||||||
@ -1637,6 +1649,10 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
|||||||
u"^stars/?(^\\?.*)?(#|$)"_q,
|
u"^stars/?(^\\?.*)?(#|$)"_q,
|
||||||
ResolveStarsSettings
|
ResolveStarsSettings
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
u"^ton/?(^\\?.*)?(#|$)"_q,
|
||||||
|
ResolveTonSettings
|
||||||
|
},
|
||||||
{
|
{
|
||||||
u"^([^\\?]+)(\\?|#|$)"_q,
|
u"^([^\\?]+)(\\?|#|$)"_q,
|
||||||
HandleUnknown
|
HandleUnknown
|
||||||
|
@ -45,10 +45,11 @@ struct UniqueGift {
|
|||||||
QString ownerName;
|
QString ownerName;
|
||||||
PeerId ownerId = 0;
|
PeerId ownerId = 0;
|
||||||
PeerData *releasedBy = nullptr;
|
PeerData *releasedBy = nullptr;
|
||||||
int number = 0;
|
int64 nanoTonForResale = -1;
|
||||||
int starsForTransfer = -1;
|
|
||||||
int starsForResale = -1;
|
int starsForResale = -1;
|
||||||
int64 tonForResale = -1;
|
int starsForTransfer = -1;
|
||||||
|
int number = 0;
|
||||||
|
bool onlyAcceptTon = false;
|
||||||
TimeId exportAt = 0;
|
TimeId exportAt = 0;
|
||||||
TimeId canTransferAt = 0;
|
TimeId canTransferAt = 0;
|
||||||
TimeId canResellAt = 0;
|
TimeId canResellAt = 0;
|
||||||
|
@ -48,6 +48,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "styles/style_settings.h"
|
#include "styles/style_settings.h"
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<CreditsAmount> StarsPriceValue(
|
||||||
|
rpl::producer<CreditsAmount> full) {
|
||||||
|
return rpl::single(
|
||||||
|
CreditsAmount()
|
||||||
|
) | rpl::then(std::move(
|
||||||
|
full
|
||||||
|
) | rpl::filter([=](CreditsAmount amount) {
|
||||||
|
return amount.stars();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<CreditsAmount> TonPriceValue(
|
||||||
|
rpl::producer<CreditsAmount> full) {
|
||||||
|
return rpl::single(
|
||||||
|
CreditsAmount()
|
||||||
|
) | rpl::then(std::move(
|
||||||
|
full
|
||||||
|
) | rpl::filter([=](CreditsAmount amount) {
|
||||||
|
return amount.ton();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void ChooseSuggestTimeBox(
|
void ChooseSuggestTimeBox(
|
||||||
not_null<Ui::GenericBox*> box,
|
not_null<Ui::GenericBox*> box,
|
||||||
@ -112,6 +137,214 @@ void AddApproximateUsd(
|
|||||||
usd->widthValue() | rpl::start_with_next(move, usd->lifetime());
|
usd->widthValue() | rpl::start_with_next(move, usd->lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StarsTonPriceInput AddStarsTonPriceInput(
|
||||||
|
not_null<Ui::VerticalLayout*> container,
|
||||||
|
StarsTonPriceArgs &&args) {
|
||||||
|
struct State {
|
||||||
|
rpl::variable<bool> ton;
|
||||||
|
rpl::variable<CreditsAmount> price;
|
||||||
|
rpl::event_stream<> updates;
|
||||||
|
rpl::event_stream<> submits;
|
||||||
|
};
|
||||||
|
const auto state = container->lifetime().make_state<State>();
|
||||||
|
state->ton = std::move(args.showTon);
|
||||||
|
state->price = args.price;
|
||||||
|
|
||||||
|
const auto session = args.session;
|
||||||
|
const auto added = st::boxRowPadding - st::defaultSubsectionTitlePadding;
|
||||||
|
const auto manager = &session->data().customEmojiManager();
|
||||||
|
const auto makeIcon = [&](
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
TextWithEntities text) {
|
||||||
|
return Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
parent,
|
||||||
|
rpl::single(text),
|
||||||
|
st::defaultFlatLabel,
|
||||||
|
st::defaultPopupMenu,
|
||||||
|
Core::TextContext({ .session = session }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto starsWrap = container->add(
|
||||||
|
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||||
|
container,
|
||||||
|
object_ptr<Ui::VerticalLayout>(container)));
|
||||||
|
const auto starsInner = starsWrap->entity();
|
||||||
|
|
||||||
|
Ui::AddSubsectionTitle(
|
||||||
|
starsInner,
|
||||||
|
tr::lng_suggest_options_stars_price(),
|
||||||
|
QMargins(
|
||||||
|
added.left(),
|
||||||
|
0,
|
||||||
|
added.right(),
|
||||||
|
-st::defaultSubsectionTitlePadding.bottom()));
|
||||||
|
|
||||||
|
const auto starsFieldWrap = starsInner->add(
|
||||||
|
object_ptr<Ui::FixedHeightWidget>(
|
||||||
|
starsInner,
|
||||||
|
st::editTagField.heightMin),
|
||||||
|
st::boxRowPadding);
|
||||||
|
auto ownedStarsField = object_ptr<Ui::NumberInput>(
|
||||||
|
starsFieldWrap,
|
||||||
|
st::editTagField,
|
||||||
|
rpl::single(u"0"_q),
|
||||||
|
((args.price && args.price.stars())
|
||||||
|
? QString::number(args.price.whole())
|
||||||
|
: QString()),
|
||||||
|
args.starsMax);
|
||||||
|
const auto starsField = ownedStarsField.data();
|
||||||
|
const auto starsIcon = makeIcon(starsField, manager->creditsEmoji());
|
||||||
|
|
||||||
|
starsFieldWrap->widthValue() | rpl::start_with_next([=](int width) {
|
||||||
|
starsIcon->move(st::starsFieldIconPosition);
|
||||||
|
starsField->move(0, 0);
|
||||||
|
starsField->resize(width, starsField->height());
|
||||||
|
starsFieldWrap->resize(width, starsField->height());
|
||||||
|
}, starsFieldWrap->lifetime());
|
||||||
|
|
||||||
|
AddApproximateUsd(
|
||||||
|
starsField,
|
||||||
|
session,
|
||||||
|
StarsPriceValue(state->price.value()));
|
||||||
|
|
||||||
|
Ui::AddSkip(starsInner);
|
||||||
|
Ui::AddSkip(starsInner);
|
||||||
|
if (args.starsAbout) {
|
||||||
|
Ui::AddDividerText(starsInner, std::move(args.starsAbout));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto tonWrap = container->add(
|
||||||
|
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||||
|
container,
|
||||||
|
object_ptr<Ui::VerticalLayout>(container)));
|
||||||
|
const auto tonInner = tonWrap->entity();
|
||||||
|
|
||||||
|
Ui::AddSubsectionTitle(
|
||||||
|
tonInner,
|
||||||
|
tr::lng_suggest_options_ton_price(),
|
||||||
|
QMargins(
|
||||||
|
added.left(),
|
||||||
|
0,
|
||||||
|
added.right(),
|
||||||
|
-st::defaultSubsectionTitlePadding.bottom()));
|
||||||
|
|
||||||
|
const auto tonFieldWrap = tonInner->add(
|
||||||
|
object_ptr<Ui::FixedHeightWidget>(
|
||||||
|
tonInner,
|
||||||
|
st::editTagField.heightMin),
|
||||||
|
st::boxRowPadding);
|
||||||
|
auto ownedTonField = object_ptr<Ui::InputField>::fromRaw(
|
||||||
|
Ui::CreateTonAmountInput(
|
||||||
|
tonFieldWrap,
|
||||||
|
rpl::single('0' + Ui::TonAmountSeparator() + '0'),
|
||||||
|
((args.price && args.price.ton())
|
||||||
|
? (args.price.whole() * Ui::kNanosInOne + args.price.nano())
|
||||||
|
: 0)));
|
||||||
|
const auto tonField = ownedTonField.data();
|
||||||
|
const auto tonIcon = makeIcon(tonField, Ui::Text::SingleCustomEmoji(
|
||||||
|
manager->registerInternalEmoji(
|
||||||
|
Ui::Earn::IconCurrencyColored(
|
||||||
|
st::tonFieldIconSize,
|
||||||
|
st::currencyFg->c),
|
||||||
|
st::channelEarnCurrencyCommonMargins,
|
||||||
|
false)));
|
||||||
|
|
||||||
|
tonFieldWrap->widthValue() | rpl::start_with_next([=](int width) {
|
||||||
|
tonIcon->move(st::tonFieldIconPosition);
|
||||||
|
tonField->move(0, 0);
|
||||||
|
tonField->resize(width, tonField->height());
|
||||||
|
tonFieldWrap->resize(width, tonField->height());
|
||||||
|
}, tonFieldWrap->lifetime());
|
||||||
|
|
||||||
|
AddApproximateUsd(
|
||||||
|
tonField,
|
||||||
|
session,
|
||||||
|
TonPriceValue(state->price.value()));
|
||||||
|
|
||||||
|
Ui::AddSkip(tonInner);
|
||||||
|
Ui::AddSkip(tonInner);
|
||||||
|
if (args.tonAbout) {
|
||||||
|
Ui::AddDividerText(tonInner, std::move(args.tonAbout));
|
||||||
|
}
|
||||||
|
|
||||||
|
tonWrap->toggleOn(state->ton.value(), anim::type::instant);
|
||||||
|
starsWrap->toggleOn(
|
||||||
|
state->ton.value() | rpl::map(!rpl::mappers::_1),
|
||||||
|
anim::type::instant);
|
||||||
|
|
||||||
|
auto computeResult = [=]() -> std::optional<CreditsAmount> {
|
||||||
|
auto nanos = int64();
|
||||||
|
const auto ton = uint32(state->ton.current() ? 1 : 0);
|
||||||
|
if (ton) {
|
||||||
|
const auto text = tonField->getLastText();
|
||||||
|
const auto now = Ui::ParseTonAmountString(text);
|
||||||
|
if (now
|
||||||
|
&& *now
|
||||||
|
&& ((*now < args.nanoTonMin) || (*now > args.nanoTonMax))) {
|
||||||
|
tonField->showError();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
nanos = now.value_or(0);
|
||||||
|
} else {
|
||||||
|
const auto now = starsField->getLastText().toLongLong();
|
||||||
|
if (now && (now < args.starsMin || now > args.starsMax)) {
|
||||||
|
starsField->showError();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
nanos = now * Ui::kNanosInOne;
|
||||||
|
}
|
||||||
|
return CreditsAmount(
|
||||||
|
nanos / Ui::kNanosInOne,
|
||||||
|
nanos % Ui::kNanosInOne,
|
||||||
|
ton ? CreditsType::Ton : CreditsType::Stars);
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto updatePrice = [=] {
|
||||||
|
if (auto result = computeResult()) {
|
||||||
|
state->price = *result;
|
||||||
|
}
|
||||||
|
state->updates.fire({});
|
||||||
|
};
|
||||||
|
QObject::connect(
|
||||||
|
starsField,
|
||||||
|
&Ui::NumberInput::changed,
|
||||||
|
starsField,
|
||||||
|
updatePrice);
|
||||||
|
tonField->changes(
|
||||||
|
) | rpl::start_with_next(updatePrice, tonField->lifetime());
|
||||||
|
|
||||||
|
state->ton.changes(
|
||||||
|
) | rpl::start_with_next(updatePrice, container->lifetime());
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
starsField,
|
||||||
|
&Ui::NumberInput::submitted,
|
||||||
|
container,
|
||||||
|
[=] { state->submits.fire({}); });
|
||||||
|
tonField->submits(
|
||||||
|
) | rpl::to_empty | rpl::start_to_stream(
|
||||||
|
state->submits,
|
||||||
|
tonField->lifetime());
|
||||||
|
|
||||||
|
auto focusCallback = [=] {
|
||||||
|
if (state->ton.current()) {
|
||||||
|
tonField->selectAll();
|
||||||
|
tonField->setFocusFast();
|
||||||
|
} else {
|
||||||
|
starsField->selectAll();
|
||||||
|
starsField->setFocusFast();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
.focusCallback = std::move(focusCallback),
|
||||||
|
.computeResult = std::move(computeResult),
|
||||||
|
.submits = state->submits.events(),
|
||||||
|
.updates = state->updates.events(),
|
||||||
|
.result = state->price.value(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
void ChooseSuggestPriceBox(
|
void ChooseSuggestPriceBox(
|
||||||
not_null<Ui::GenericBox*> box,
|
not_null<Ui::GenericBox*> box,
|
||||||
SuggestPriceBoxArgs &&args) {
|
SuggestPriceBoxArgs &&args) {
|
||||||
@ -135,42 +368,17 @@ void ChooseSuggestPriceBox(
|
|||||||
state->date = args.value.date;
|
state->date = args.value.date;
|
||||||
state->ton = (args.value.ton != 0);
|
state->ton = (args.value.ton != 0);
|
||||||
state->price = args.value.price();
|
state->price = args.value.price();
|
||||||
const auto updatePrice = [=] {
|
|
||||||
if (const auto price = state->computePrice()) {
|
|
||||||
state->price = *price;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto starsPrice = [=] {
|
|
||||||
return rpl::single(
|
|
||||||
CreditsAmount()
|
|
||||||
) | rpl::then(state->price.value(
|
|
||||||
) | rpl::filter([=](CreditsAmount amount) {
|
|
||||||
return amount.stars();
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
const auto tonPrice = [=] {
|
|
||||||
return rpl::single(
|
|
||||||
CreditsAmount(0, 0, CreditsType::Ton)
|
|
||||||
) | rpl::then(state->price.value(
|
|
||||||
) | rpl::filter([=](CreditsAmount amount) {
|
|
||||||
return amount.ton();
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto peer = args.peer;
|
const auto peer = args.peer;
|
||||||
const auto admin = peer->amMonoforumAdmin();
|
const auto admin = peer->amMonoforumAdmin();
|
||||||
const auto broadcast = peer->monoforumBroadcast();
|
const auto broadcast = peer->monoforumBroadcast();
|
||||||
const auto usePeer = broadcast ? broadcast : peer;
|
const auto usePeer = broadcast ? broadcast : peer;
|
||||||
const auto session = &peer->session();
|
const auto session = &peer->session();
|
||||||
|
const auto &appConfig = session->appConfig();
|
||||||
if (!admin) {
|
if (!admin) {
|
||||||
session->credits().load();
|
session->credits().load();
|
||||||
session->credits().tonLoad();
|
session->credits().tonLoad();
|
||||||
}
|
}
|
||||||
const auto starsMin = session->appConfig().suggestedPostStarsMin();
|
|
||||||
const auto starsMax = session->appConfig().suggestedPostStarsMax();
|
|
||||||
const auto nanoTonMin = session->appConfig().suggestedPostNanoTonMin();
|
|
||||||
const auto nanoTonMax = session->appConfig().suggestedPostNanoTonMax();
|
|
||||||
const auto container = box->verticalLayout();
|
const auto container = box->verticalLayout();
|
||||||
|
|
||||||
box->setStyle(st::suggestPriceBox);
|
box->setStyle(st::suggestPriceBox);
|
||||||
@ -267,7 +475,6 @@ void ChooseSuggestPriceBox(
|
|||||||
state->buttons[i].active = true;
|
state->buttons[i].active = true;
|
||||||
state->buttons[1 - i].active = false;
|
state->buttons[1 - i].active = false;
|
||||||
buttons->update();
|
buttons->update();
|
||||||
updatePrice();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,64 +506,11 @@ void ChooseSuggestPriceBox(
|
|||||||
|
|
||||||
Ui::AddSkip(container);
|
Ui::AddSkip(container);
|
||||||
|
|
||||||
const auto added = st::boxRowPadding - st::defaultSubsectionTitlePadding;
|
const auto computePrice = [session](CreditsAmount amount) {
|
||||||
const auto manager = &session->data().customEmojiManager();
|
return PriceAfterCommission(session, amount).value();
|
||||||
const auto makeIcon = [&](
|
|
||||||
not_null<QWidget*> parent,
|
|
||||||
TextWithEntities text) {
|
|
||||||
return Ui::CreateChild<Ui::FlatLabel>(
|
|
||||||
parent,
|
|
||||||
rpl::single(text),
|
|
||||||
st::defaultFlatLabel,
|
|
||||||
st::defaultPopupMenu,
|
|
||||||
Core::TextContext({ .session = session }));
|
|
||||||
};
|
};
|
||||||
|
const auto formatCommission = [session](CreditsAmount amount) {
|
||||||
const auto starsWrap = container->add(
|
return FormatAfterCommissionPercent(session, amount);
|
||||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
|
||||||
container,
|
|
||||||
object_ptr<Ui::VerticalLayout>(container)));
|
|
||||||
const auto starsInner = starsWrap->entity();
|
|
||||||
|
|
||||||
Ui::AddSubsectionTitle(
|
|
||||||
starsInner,
|
|
||||||
tr::lng_suggest_options_stars_price(),
|
|
||||||
QMargins(added.left(), 0, added.right(), -st::defaultSubsectionTitlePadding.bottom()));
|
|
||||||
|
|
||||||
const auto starsFieldWrap = starsInner->add(
|
|
||||||
object_ptr<Ui::FixedHeightWidget>(
|
|
||||||
box,
|
|
||||||
st::editTagField.heightMin),
|
|
||||||
st::boxRowPadding);
|
|
||||||
auto ownedStarsField = object_ptr<Ui::NumberInput>(
|
|
||||||
starsFieldWrap,
|
|
||||||
st::editTagField,
|
|
||||||
rpl::single(u"0"_q),
|
|
||||||
((args.value.exists && args.value.priceWhole && !args.value.ton)
|
|
||||||
? QString::number(args.value.priceWhole)
|
|
||||||
: QString()),
|
|
||||||
starsMax);
|
|
||||||
const auto starsField = ownedStarsField.data();
|
|
||||||
const auto starsIcon = makeIcon(starsField, manager->creditsEmoji());
|
|
||||||
|
|
||||||
QObject::connect(starsField, &Ui::NumberInput::changed, updatePrice);
|
|
||||||
|
|
||||||
starsFieldWrap->widthValue() | rpl::start_with_next([=](int width) {
|
|
||||||
starsIcon->move(st::starsFieldIconPosition);
|
|
||||||
starsField->move(0, 0);
|
|
||||||
starsField->resize(width, starsField->height());
|
|
||||||
starsFieldWrap->resize(width, starsField->height());
|
|
||||||
}, starsFieldWrap->lifetime());
|
|
||||||
|
|
||||||
AddApproximateUsd(starsField, session, starsPrice());
|
|
||||||
|
|
||||||
Ui::AddSkip(starsInner);
|
|
||||||
Ui::AddSkip(starsInner);
|
|
||||||
const auto computePrice = [peer = args.peer](CreditsAmount amount) {
|
|
||||||
return PriceAfterCommission(&peer->session(), amount).value();
|
|
||||||
};
|
|
||||||
const auto formatCommission = [peer = args.peer](CreditsAmount amount) {
|
|
||||||
return FormatAfterCommissionPercent(&peer->session(), amount);
|
|
||||||
};
|
};
|
||||||
const auto youGet = [=](rpl::producer<CreditsAmount> price, bool stars) {
|
const auto youGet = [=](rpl::producer<CreditsAmount> price, bool stars) {
|
||||||
return (stars
|
return (stars
|
||||||
@ -367,109 +521,34 @@ void ChooseSuggestPriceBox(
|
|||||||
lt_percent,
|
lt_percent,
|
||||||
rpl::duplicate(price) | rpl::map(formatCommission));
|
rpl::duplicate(price) | rpl::map(formatCommission));
|
||||||
};
|
};
|
||||||
Ui::AddDividerText(starsInner, admin
|
auto starsAbout = admin
|
||||||
? rpl::combine(
|
? rpl::combine(
|
||||||
youGet(starsPrice(), true),
|
youGet(StarsPriceValue(state->price.value()), true),
|
||||||
tr::lng_suggest_options_stars_warning(Ui::Text::RichLangValue)
|
tr::lng_suggest_options_stars_warning(Ui::Text::RichLangValue)
|
||||||
) | rpl::map([=](const QString &t1, const TextWithEntities &t2) {
|
) | rpl::map([=](const QString &t1, const TextWithEntities &t2) {
|
||||||
return TextWithEntities{ t1 }.append("\n\n").append(t2);
|
return TextWithEntities{ t1 }.append("\n\n").append(t2);
|
||||||
})
|
})
|
||||||
: tr::lng_suggest_options_stars_price_about(Ui::Text::WithEntities));
|
: tr::lng_suggest_options_stars_price_about(Ui::Text::WithEntities);
|
||||||
|
auto tonAbout = admin
|
||||||
const auto tonWrap = container->add(
|
? youGet(
|
||||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
TonPriceValue(state->price.value()),
|
||||||
container,
|
false
|
||||||
object_ptr<Ui::VerticalLayout>(container)));
|
) | Ui::Text::ToWithEntities()
|
||||||
const auto tonInner = tonWrap->entity();
|
: tr::lng_suggest_options_ton_price_about(Ui::Text::WithEntities);
|
||||||
|
auto priceInput = AddStarsTonPriceInput(container, {
|
||||||
Ui::AddSubsectionTitle(
|
.session = session,
|
||||||
tonInner,
|
.showTon = state->ton.value(),
|
||||||
tr::lng_suggest_options_ton_price(),
|
.price = args.value.price(),
|
||||||
QMargins(added.left(), 0, added.right(), -st::defaultSubsectionTitlePadding.bottom()));
|
.starsMin = appConfig.suggestedPostStarsMin(),
|
||||||
|
.starsMax = appConfig.suggestedPostStarsMax(),
|
||||||
const auto tonFieldWrap = tonInner->add(
|
.nanoTonMin = appConfig.suggestedPostNanoTonMin(),
|
||||||
object_ptr<Ui::FixedHeightWidget>(
|
.nanoTonMax = appConfig.suggestedPostNanoTonMax(),
|
||||||
box,
|
.starsAbout = std::move(starsAbout),
|
||||||
st::editTagField.heightMin),
|
.tonAbout = std::move(tonAbout),
|
||||||
st::boxRowPadding);
|
|
||||||
auto ownedTonField = object_ptr<Ui::InputField>::fromRaw(
|
|
||||||
Ui::CreateTonAmountInput(
|
|
||||||
tonFieldWrap,
|
|
||||||
rpl::single('0' + Ui::TonAmountSeparator() + '0'),
|
|
||||||
((args.value.price() && args.value.ton)
|
|
||||||
? (int64(args.value.priceWhole) * Ui::kNanosInOne
|
|
||||||
+ int64(args.value.priceNano))
|
|
||||||
: 0)));
|
|
||||||
const auto tonField = ownedTonField.data();
|
|
||||||
const auto tonIcon = makeIcon(tonField, Ui::Text::SingleCustomEmoji(
|
|
||||||
manager->registerInternalEmoji(
|
|
||||||
Ui::Earn::IconCurrencyColored(
|
|
||||||
st::tonFieldIconSize,
|
|
||||||
st::currencyFg->c),
|
|
||||||
st::channelEarnCurrencyCommonMargins,
|
|
||||||
false)));
|
|
||||||
|
|
||||||
tonField->changes(
|
|
||||||
) | rpl::start_with_next(updatePrice, tonField->lifetime());
|
|
||||||
|
|
||||||
tonFieldWrap->widthValue() | rpl::start_with_next([=](int width) {
|
|
||||||
tonIcon->move(st::tonFieldIconPosition);
|
|
||||||
tonField->move(0, 0);
|
|
||||||
tonField->resize(width, tonField->height());
|
|
||||||
tonFieldWrap->resize(width, tonField->height());
|
|
||||||
}, tonFieldWrap->lifetime());
|
|
||||||
|
|
||||||
AddApproximateUsd(tonField, session, tonPrice());
|
|
||||||
|
|
||||||
Ui::AddSkip(tonInner);
|
|
||||||
Ui::AddSkip(tonInner);
|
|
||||||
Ui::AddDividerText(
|
|
||||||
tonInner,
|
|
||||||
(admin
|
|
||||||
? youGet(tonPrice(), false)
|
|
||||||
: tr::lng_suggest_options_ton_price_about()));
|
|
||||||
|
|
||||||
tonWrap->toggleOn(state->ton.value(), anim::type::instant);
|
|
||||||
starsWrap->toggleOn(
|
|
||||||
state->ton.value() | rpl::map(!rpl::mappers::_1),
|
|
||||||
anim::type::instant);
|
|
||||||
|
|
||||||
state->computePrice = [=]() -> std::optional<CreditsAmount> {
|
|
||||||
auto nanos = int64();
|
|
||||||
const auto ton = uint32(state->ton.current() ? 1 : 0);
|
|
||||||
if (ton) {
|
|
||||||
const auto text = tonField->getLastText();
|
|
||||||
const auto now = Ui::ParseTonAmountString(text);
|
|
||||||
if (now
|
|
||||||
&& *now
|
|
||||||
&& ((*now < nanoTonMin) || (*now > nanoTonMax))) {
|
|
||||||
tonField->showError();
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
nanos = now.value_or(0);
|
|
||||||
} else {
|
|
||||||
const auto now = starsField->getLastText().toLongLong();
|
|
||||||
if (now && (now < starsMin || now > starsMax)) {
|
|
||||||
starsField->showError();
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
nanos = now * Ui::kNanosInOne;
|
|
||||||
}
|
|
||||||
return CreditsAmount(
|
|
||||||
nanos / Ui::kNanosInOne,
|
|
||||||
nanos % Ui::kNanosInOne,
|
|
||||||
ton ? CreditsType::Ton : CreditsType::Stars);
|
|
||||||
};
|
|
||||||
|
|
||||||
box->setFocusCallback([=] {
|
|
||||||
if (state->ton.current()) {
|
|
||||||
tonField->selectAll();
|
|
||||||
tonField->setFocusFast();
|
|
||||||
} else {
|
|
||||||
starsField->selectAll();
|
|
||||||
starsField->setFocusFast();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
state->price = std::move(priceInput.result);
|
||||||
|
state->computePrice = std::move(priceInput.computeResult);
|
||||||
|
box->setFocusCallback(std::move(priceInput.focusCallback));
|
||||||
|
|
||||||
Ui::AddSkip(container);
|
Ui::AddSkip(container);
|
||||||
|
|
||||||
@ -568,17 +647,13 @@ void ChooseSuggestPriceBox(
|
|||||||
}
|
}
|
||||||
}, box->lifetime());
|
}, box->lifetime());
|
||||||
|
|
||||||
QObject::connect(
|
std::move(
|
||||||
starsField,
|
priceInput.submits
|
||||||
&Ui::NumberInput::submitted,
|
) | rpl::start_with_next(state->save, box->lifetime());
|
||||||
box,
|
|
||||||
state->save);
|
|
||||||
tonField->submits(
|
|
||||||
) | rpl::start_with_next(state->save, tonField->lifetime());
|
|
||||||
|
|
||||||
const auto button = box->addButton(rpl::single(QString()), state->save);
|
const auto button = box->addButton(rpl::single(QString()), state->save);
|
||||||
const auto coloredTonIcon = Ui::Text::SingleCustomEmoji(
|
const auto coloredTonIcon = Ui::Text::SingleCustomEmoji(
|
||||||
manager->registerInternalEmoji(
|
session->data().customEmojiManager().registerInternalEmoji(
|
||||||
Ui::Earn::IconCurrencyColored(
|
Ui::Earn::IconCurrencyColored(
|
||||||
st::tonFieldIconSize,
|
st::tonFieldIconSize,
|
||||||
st::currencyFg->c),
|
st::currencyFg->c),
|
||||||
|
@ -15,6 +15,7 @@ class Show;
|
|||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class GenericBox;
|
class GenericBox;
|
||||||
|
class VerticalLayout;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
@ -43,6 +44,30 @@ void ChooseSuggestTimeBox(
|
|||||||
not_null<Ui::GenericBox*> box,
|
not_null<Ui::GenericBox*> box,
|
||||||
SuggestTimeBoxArgs &&args);
|
SuggestTimeBoxArgs &&args);
|
||||||
|
|
||||||
|
struct StarsTonPriceInput {
|
||||||
|
Fn<void()> focusCallback;
|
||||||
|
Fn<std::optional<CreditsAmount>()> computeResult;
|
||||||
|
rpl::producer<> submits;
|
||||||
|
rpl::producer<> updates;
|
||||||
|
rpl::producer<CreditsAmount> result;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StarsTonPriceArgs {
|
||||||
|
not_null<Main::Session*> session;
|
||||||
|
rpl::producer<bool> showTon;
|
||||||
|
CreditsAmount price;
|
||||||
|
int starsMin = 0;
|
||||||
|
int starsMax = 0;
|
||||||
|
int64 nanoTonMin = 0;
|
||||||
|
int64 nanoTonMax = 0;
|
||||||
|
rpl::producer<TextWithEntities> starsAbout;
|
||||||
|
rpl::producer<TextWithEntities> tonAbout;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] StarsTonPriceInput AddStarsTonPriceInput(
|
||||||
|
not_null<Ui::VerticalLayout*> container,
|
||||||
|
StarsTonPriceArgs &&args);
|
||||||
|
|
||||||
struct SuggestPriceBoxArgs {
|
struct SuggestPriceBoxArgs {
|
||||||
not_null<PeerData*> peer;
|
not_null<PeerData*> peer;
|
||||||
bool updating = false;
|
bool updating = false;
|
||||||
|
@ -96,6 +96,13 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
|
|||||||
_mediaLifetime.destroy();
|
_mediaLifetime.destroy();
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
|
|
||||||
|
const auto format = [=](int64 number) {
|
||||||
|
const auto onlyK = (number < 100'000'000);
|
||||||
|
return (number >= 1'000'000)
|
||||||
|
? Lang::FormatCountToShort(number, onlyK).string
|
||||||
|
: Lang::FormatCountDecimal(number);
|
||||||
|
};
|
||||||
|
|
||||||
_descriptor = descriptor;
|
_descriptor = descriptor;
|
||||||
_resalePrice = resalePrice;
|
_resalePrice = resalePrice;
|
||||||
const auto resale = (_resalePrice > 0);
|
const auto resale = (_resalePrice > 0);
|
||||||
@ -156,19 +163,18 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
|
|||||||
? (unique
|
? (unique
|
||||||
? _delegate->monostar()
|
? _delegate->monostar()
|
||||||
: _delegate->star()).append(' ').append(
|
: _delegate->star()).append(' ').append(
|
||||||
Lang::FormatCountDecimal(unique
|
format(unique
|
||||||
? unique->starsForResale
|
? unique->starsForResale
|
||||||
: data.info.starsResellMin)
|
: data.info.starsResellMin)
|
||||||
).append(data.info.resellCount > 1 ? "+" : "")
|
).append(data.info.resellCount > 1 ? "+" : "")
|
||||||
: (_small && unique && unique->starsForResale)
|
: (_small && unique && unique->starsForResale)
|
||||||
? _delegate->monostar().append(' ').append(
|
? _delegate->monostar().append(' ').append(
|
||||||
Lang::FormatCountDecimal(unique->starsForResale))
|
format(unique->starsForResale))
|
||||||
: unique
|
: unique
|
||||||
? tr::lng_gift_transfer_button(
|
? tr::lng_gift_transfer_button(
|
||||||
tr::now,
|
tr::now,
|
||||||
Ui::Text::WithEntities)
|
Ui::Text::WithEntities)
|
||||||
: _delegate->star().append(
|
: _delegate->star().append(' ' + format(data.info.stars))),
|
||||||
' ' + Lang::FormatCountDecimal(data.info.stars))),
|
|
||||||
kMarkupTextOptions,
|
kMarkupTextOptions,
|
||||||
_delegate->textContext());
|
_delegate->textContext());
|
||||||
if (!_stars) {
|
if (!_stars) {
|
||||||
|
@ -917,7 +917,7 @@ int NonZeroPartToInt(QString value) {
|
|||||||
: (value.isEmpty() ? 0 : value.toInt());
|
: (value.isEmpty() ? 0 : value.toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
ShortenedCount FormatCountToShort(int64 number) {
|
ShortenedCount FormatCountToShort(int64 number, bool onlyK) {
|
||||||
auto result = ShortenedCount{ number };
|
auto result = ShortenedCount{ number };
|
||||||
const auto abs = std::abs(number);
|
const auto abs = std::abs(number);
|
||||||
const auto shorten = [&](int64 divider, char multiplier) {
|
const auto shorten = [&](int64 divider, char multiplier) {
|
||||||
@ -934,7 +934,7 @@ ShortenedCount FormatCountToShort(int64 number) {
|
|||||||
result.number = rounded * divider;
|
result.number = rounded * divider;
|
||||||
result.shortened = true;
|
result.shortened = true;
|
||||||
};
|
};
|
||||||
if (abs >= 1'000'000) {
|
if (!onlyK && abs >= 1'000'000) {
|
||||||
shorten(1'000'000, 'M');
|
shorten(1'000'000, 'M');
|
||||||
} else if (abs >= 10'000) {
|
} else if (abs >= 10'000) {
|
||||||
shorten(1'000, 'K');
|
shorten(1'000, 'K');
|
||||||
|
@ -26,7 +26,9 @@ struct ShortenedCount {
|
|||||||
QString string;
|
QString string;
|
||||||
bool shortened = false;
|
bool shortened = false;
|
||||||
};
|
};
|
||||||
[[nodiscard]] ShortenedCount FormatCountToShort(int64 number);
|
[[nodiscard]] ShortenedCount FormatCountToShort(
|
||||||
|
int64 number,
|
||||||
|
bool onlyK = false);
|
||||||
[[nodiscard]] QString FormatCountDecimal(int64 number);
|
[[nodiscard]] QString FormatCountDecimal(int64 number);
|
||||||
[[nodiscard]] QString FormatExactCountDecimal(float64 number);
|
[[nodiscard]] QString FormatExactCountDecimal(float64 number);
|
||||||
[[nodiscard]] ShortenedCount FormatCreditsAmountToShort(
|
[[nodiscard]] ShortenedCount FormatCreditsAmountToShort(
|
||||||
|
@ -148,18 +148,32 @@ bool AppConfig::confcallPrioritizeVP8() const {
|
|||||||
return get<bool>(u"confcall_use_vp8"_q, false);
|
return get<bool>(u"confcall_use_vp8"_q, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
int AppConfig::giftResalePriceMax() const {
|
int AppConfig::giftResaleStarsMin() const {
|
||||||
return get<int>(u"stars_stargift_resale_amount_max"_q, 35000);
|
|
||||||
}
|
|
||||||
|
|
||||||
int AppConfig::giftResalePriceMin() const {
|
|
||||||
return get<int>(u"stars_stargift_resale_amount_min"_q, 125);
|
return get<int>(u"stars_stargift_resale_amount_min"_q, 125);
|
||||||
}
|
}
|
||||||
|
|
||||||
int AppConfig::giftResaleReceiveThousandths() const {
|
int AppConfig::giftResaleStarsMax() const {
|
||||||
|
return get<int>(u"stars_stargift_resale_amount_max"_q, 35000);
|
||||||
|
}
|
||||||
|
|
||||||
|
int AppConfig::giftResaleStarsThousandths() const {
|
||||||
return get<int>(u"stars_stargift_resale_commission_permille"_q, 800);
|
return get<int>(u"stars_stargift_resale_commission_permille"_q, 800);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int64 AppConfig::giftResaleNanoTonMin() const {
|
||||||
|
return get<int64>(u"ton_stargift_resale_amount_min"_q, 250'000'000LL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64 AppConfig::giftResaleNanoTonMax() const {
|
||||||
|
return get<int64>(
|
||||||
|
u"ton_stargift_resale_amount_max"_q,
|
||||||
|
1'000'000'000'000'000LL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int AppConfig::giftResaleNanoTonThousandths() const {
|
||||||
|
return get<int>(u"ton_stargift_resale_commission_permille"_q, 800);
|
||||||
|
}
|
||||||
|
|
||||||
int AppConfig::pollOptionsLimit() const {
|
int AppConfig::pollOptionsLimit() const {
|
||||||
return get<int>(u"poll_answers_max"_q, 12);
|
return get<int>(u"poll_answers_max"_q, 12);
|
||||||
}
|
}
|
||||||
|
@ -85,9 +85,12 @@ public:
|
|||||||
[[nodiscard]] int confcallSizeLimit() const;
|
[[nodiscard]] int confcallSizeLimit() const;
|
||||||
[[nodiscard]] bool confcallPrioritizeVP8() const;
|
[[nodiscard]] bool confcallPrioritizeVP8() const;
|
||||||
|
|
||||||
[[nodiscard]] int giftResalePriceMax() const;
|
[[nodiscard]] int giftResaleStarsMin() const;
|
||||||
[[nodiscard]] int giftResalePriceMin() const;
|
[[nodiscard]] int giftResaleStarsMax() const;
|
||||||
[[nodiscard]] int giftResaleReceiveThousandths() const;
|
[[nodiscard]] int giftResaleStarsThousandths() const;
|
||||||
|
[[nodiscard]] int64 giftResaleNanoTonMin() const;
|
||||||
|
[[nodiscard]] int64 giftResaleNanoTonMax() const;
|
||||||
|
[[nodiscard]] int giftResaleNanoTonThousandths() const;
|
||||||
|
|
||||||
[[nodiscard]] int pollOptionsLimit() const;
|
[[nodiscard]] int pollOptionsLimit() const;
|
||||||
[[nodiscard]] int todoListItemsLimit() const;
|
[[nodiscard]] int todoListItemsLimit() const;
|
||||||
|
@ -1081,7 +1081,7 @@ void FillUniqueGiftMenu(
|
|||||||
const auto name = UniqueGiftName(*unique);
|
const auto name = UniqueGiftName(*unique);
|
||||||
const auto confirm = [=](Fn<void()> close) {
|
const auto confirm = [=](Fn<void()> close) {
|
||||||
close();
|
close();
|
||||||
Ui::UpdateGiftSellPrice(show, unique, savedId, 0);
|
Ui::UpdateGiftSellPrice(show, unique, savedId, {});
|
||||||
};
|
};
|
||||||
show->show(Ui::MakeConfirmBox({
|
show->show(Ui::MakeConfirmBox({
|
||||||
.text = tr::lng_gift_sell_unlist_sure(),
|
.text = tr::lng_gift_sell_unlist_sure(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user