Start auction preview display.
This commit is contained in:
@@ -3736,6 +3736,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_stars_auction" = "auction";
|
||||
"lng_gift_stars_auction_join" = "Join";
|
||||
"lng_gift_stars_auction_view" = "View";
|
||||
"lng_gift_stars_auction_soon" = "soon";
|
||||
"lng_gift_stars_auction_upgraded" = "upgraded";
|
||||
"lng_gift_stars_your_left#one" = "{count} left";
|
||||
"lng_gift_stars_your_left#other" = "{count} left";
|
||||
"lng_gift_stars_your_finished" = "none left";
|
||||
@@ -4082,6 +4084,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_auction_text_link" = "Learn more {arrow}";
|
||||
"lng_auction_text_ended" = "Auction ended.";
|
||||
"lng_auction_start_label" = "Started";
|
||||
"lng_auction_starts_label" = "Starts";
|
||||
"lng_auction_rounds_label" = "Rounds";
|
||||
"lng_auction_rounds_first" = "Round 1";
|
||||
"lng_auction_rounds_rest" = "Rounds 2-{last}";
|
||||
"lng_auction_rounds_rest_minutes#one" = "{count} minute each";
|
||||
"lng_auction_rounds_rest_minutes#other" = "{count} minutes each";
|
||||
"lng_auction_rounds_rest_hours#one" = "{count} hour each";
|
||||
"lng_auction_rounds_rest_hours#other" = "{count} hours each";
|
||||
"lng_auction_end_label" = "Ends";
|
||||
"lng_auction_round_label" = "Current Round";
|
||||
"lng_auction_round_value" = "{n} of {amount}";
|
||||
@@ -4096,10 +4106,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_auction_join_time_left" = "{time} left";
|
||||
"lng_auction_join_time_medium" = "{hours} h {minutes} m";
|
||||
"lng_auction_join_time_small" = "{minutes} m";
|
||||
"lng_auction_join_starts_in" = "starts in {time}";
|
||||
"lng_auction_join_early_bid" = "Place an Early Bid";
|
||||
"lng_auction_menu_about" = "About";
|
||||
"lng_auction_menu_copy_link" = "Copy Link";
|
||||
"lng_auction_menu_share" = "Share";
|
||||
"lng_auction_bid_title" = "Place a Bid";
|
||||
"lng_auction_bid_title_early" = "Place an Early Bid";
|
||||
"lng_auction_bid_subtitle#one" = "Top {count} bidder will win";
|
||||
"lng_auction_bid_subtitle#other" = "Top {count} bidders will win";
|
||||
"lng_auction_bid_your" = "your bid";
|
||||
@@ -4154,6 +4167,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_auction_change_to" = "Do you want to raise your bid and change the recipient to {name}?";
|
||||
"lng_auction_change_already_me" = "You've already placed a bid on this gift for yourself.";
|
||||
"lng_auction_change_to_me" = "Do you want to raise your bid and change the recipient to yourself?";
|
||||
"lng_auction_preview_name" = "Upcoming Auction";
|
||||
"lng_auction_preview_learn_gifts" = "Learn more about Telegram Gifts {arrow}";
|
||||
"lng_auction_preview_variants#one" = "View {emoji} {count} Variant {arrow}";
|
||||
"lng_auction_preview_variants#other" = "View {emoji} {count} Variants {arrow}";
|
||||
"lng_auction_preview_random" = "Random Traits";
|
||||
"lng_auction_preview_selected" = "Selected Traits";
|
||||
"lng_auction_preview_randomize" = "Randomize Traits";
|
||||
"lng_auction_preview_model" = "model";
|
||||
"lng_auction_preview_models#one" = "This collectible features **{count}** unique model.";
|
||||
"lng_auction_preview_models#other" = "This collectible features **{count}** unique models.";
|
||||
"lng_auction_preview_backdrop" = "backdrop";
|
||||
"lng_auction_preview_backdrops#one" = "This collectible features **{count}** unique backdrop.";
|
||||
"lng_auction_preview_backdrops#other" = "This collectible features **{count}** unique backdrops.";
|
||||
"lng_auction_preview_symbol" = "symbol";
|
||||
"lng_auction_preview_symbols#one" = "This collectible features **{count}** unique symbol.";
|
||||
"lng_auction_preview_symbols#other" = "This collectible features **{count}** unique symbols.";
|
||||
"lng_auction_preview_wear" = "More about wearing gifts {arrow}";
|
||||
"lng_auction_preview_free_upgrade" = "You can upgrade your gift for free, sell it on the market, or set it as your profile cover.";
|
||||
|
||||
"lng_accounts_limit_title" = "Limit Reached";
|
||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
|
||||
|
||||
@@ -874,10 +874,12 @@ std::optional<Data::StarGift> FromTL(
|
||||
.resellCount = int(data.vavailability_resale().value_or_empty()),
|
||||
.auctionSlug = qs(data.vauction_slug().value_or_empty()),
|
||||
.auctionGiftsPerRound = data.vgifts_per_round().value_or_empty(),
|
||||
.auctionStartDate = data.vauction_start_date().value_or_empty(),
|
||||
.limitedLeft = remaining.value_or_empty(),
|
||||
.limitedCount = total.value_or_empty(),
|
||||
.perUserTotal = data.vper_user_total().value_or_empty(),
|
||||
.perUserRemains = data.vper_user_remains().value_or_empty(),
|
||||
.upgradeVariants = data.vupgrade_variants().value_or_empty(),
|
||||
.firstSaleDate = data.vfirst_sale_date().value_or_empty(),
|
||||
.lastSaleDate = data.vlast_sale_date().value_or_empty(),
|
||||
.lockedUntilDate = data.vlocked_until_date().value_or_empty(),
|
||||
@@ -957,6 +959,8 @@ std::optional<Data::StarGift> FromTL(
|
||||
data.vvalue_currency().value_or_empty()),
|
||||
.valuePrice = int64(
|
||||
data.vvalue_amount().value_or_empty()),
|
||||
.valuePriceUsd = int64(
|
||||
data.vvalue_usd_amount().value_or_empty()),
|
||||
})
|
||||
: nullptr),
|
||||
.peerColor = colorCollectible,
|
||||
@@ -1024,6 +1028,7 @@ std::optional<Data::SavedStarGift> FromTL(
|
||||
? peerFromMTP(*data.vfrom_id())
|
||||
: PeerId()),
|
||||
.date = data.vdate().v,
|
||||
.giftNum = data.vgift_num().value_or_empty(),
|
||||
.upgradeSeparate = data.is_upgrade_separate(),
|
||||
.upgradable = data.is_can_upgrade(),
|
||||
.anonymous = data.is_name_hidden(),
|
||||
|
||||
@@ -8,12 +8,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/star_gift_auction_box.h"
|
||||
|
||||
#include "api/api_text_entities.h"
|
||||
#include "base/random.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/peers/replace_boost_box.h"
|
||||
#include "boxes/send_credits_box.h" // CreditsEmojiSmall
|
||||
#include "boxes/share_box.h"
|
||||
#include "boxes/star_gift_box.h"
|
||||
#include "boxes/star_gift_resale_box.h"
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "core/application.h"
|
||||
#include "core/credits_amount.h"
|
||||
@@ -74,6 +76,7 @@ namespace {
|
||||
|
||||
constexpr auto kAuctionAboutShownPref = "gift_auction_about_shown"_cs;
|
||||
constexpr auto kBidPlacedToastDuration = 5 * crl::time(1000);
|
||||
constexpr auto kSwitchPreviewCoverInterval = 3 * crl::time(1000);
|
||||
constexpr auto kMaxShownBid = 30'000;
|
||||
constexpr auto kShowTopPlaces = 3;
|
||||
|
||||
@@ -288,33 +291,39 @@ struct BidSliderValues {
|
||||
return result;
|
||||
}
|
||||
|
||||
Fn<void()> MakeAuctionMenuCallback(
|
||||
not_null<QWidget*> parent,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> MakeAuctionFillMenuCallback(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
const Data::GiftAuctionState &state) {
|
||||
const auto url = show->session().createInternalLinkFull(
|
||||
u"auction/"_q + state.gift->auctionSlug);
|
||||
const auto rounds = state.totalRounds;
|
||||
const auto perRound = state.gift->auctionGiftsPerRound;;
|
||||
const auto menu = std::make_shared<base::unique_qptr<PopupMenu>>();
|
||||
return [=] {
|
||||
*menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
parent,
|
||||
st::popupMenuWithIcons);
|
||||
|
||||
(*menu)->addAction(tr::lng_auction_menu_about(tr::now), [=] {
|
||||
return [=](not_null<Ui::PopupMenu*> menu) {
|
||||
menu->addAction(tr::lng_auction_menu_about(tr::now), [=] {
|
||||
show->show(Box(AuctionAboutBox, rounds, perRound, nullptr));
|
||||
}, &st::menuIconInfo);
|
||||
|
||||
(*menu)->addAction(tr::lng_auction_menu_copy_link(tr::now), [=] {
|
||||
menu->addAction(tr::lng_auction_menu_copy_link(tr::now), [=] {
|
||||
QApplication::clipboard()->setText(url);
|
||||
show->showToast(tr::lng_username_copied(tr::now));
|
||||
}, &st::menuIconLink);
|
||||
|
||||
(*menu)->addAction(tr::lng_auction_menu_share(tr::now), [=] {
|
||||
menu->addAction(tr::lng_auction_menu_share(tr::now), [=] {
|
||||
FastShareLink(show, url);
|
||||
}, &st::menuIconShare);
|
||||
};
|
||||
}
|
||||
|
||||
Fn<void()> MakeAuctionMenuCallback(
|
||||
not_null<QWidget*> parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
const Data::GiftAuctionState &state) {
|
||||
const auto menu = std::make_shared<base::unique_qptr<PopupMenu>>();
|
||||
return [=, fill = MakeAuctionFillMenuCallback(show, state)] {
|
||||
*menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
parent,
|
||||
st::popupMenuWithIcons);
|
||||
fill(menu->get());
|
||||
(*menu)->popup(QCursor::pos());
|
||||
};
|
||||
}
|
||||
@@ -906,6 +915,30 @@ void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
|
||||
helper.context());
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<int> RandomIndicesSubset(int total, int subset) {
|
||||
const auto take = std::min(total, subset);
|
||||
if (!take) {
|
||||
return {};
|
||||
}
|
||||
auto result = std::vector<int>();
|
||||
auto taken = base::flat_set<int>();
|
||||
result.reserve(take);
|
||||
taken.reserve(take);
|
||||
for (auto i = 0; i < take; ++i) {
|
||||
auto index = base::RandomIndex(total - i);
|
||||
for (const auto already : taken) {
|
||||
if (index >= already) {
|
||||
++index;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
taken.emplace(index);
|
||||
result.push_back(index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<TableLayout> AuctionInfoTable(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<VerticalLayout*> container,
|
||||
@@ -921,6 +954,7 @@ void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
|
||||
state->value = std::move(value);
|
||||
|
||||
const auto &now = state->value.current();
|
||||
const auto preview = (now.startDate > base::unixtime::now());
|
||||
const auto name = now.gift->resellTitle;
|
||||
state->finished = now.finished()
|
||||
? (rpl::single(true) | rpl::type_erased())
|
||||
@@ -932,10 +966,12 @@ void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
|
||||
};
|
||||
AddTableRow(
|
||||
raw,
|
||||
rpl::conditional(
|
||||
state->finished.value(),
|
||||
tr::lng_gift_link_label_first_sale(),
|
||||
tr::lng_auction_start_label()),
|
||||
(preview
|
||||
? tr::lng_auction_starts_label()
|
||||
: rpl::conditional(
|
||||
state->finished.value(),
|
||||
tr::lng_gift_link_label_first_sale(),
|
||||
tr::lng_auction_start_label())),
|
||||
date(now.startDate));
|
||||
AddTableRow(
|
||||
raw,
|
||||
@@ -944,65 +980,104 @@ void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
|
||||
tr::lng_gift_link_label_last_sale(),
|
||||
tr::lng_auction_end_label()),
|
||||
date(now.endDate));
|
||||
|
||||
auto roundText = state->value.value(
|
||||
) | rpl::map([](const Data::GiftAuctionState &state) {
|
||||
const auto wrapped = [](int count) {
|
||||
return rpl::single(tr::marked(Lang::FormatCountDecimal(count)));
|
||||
};
|
||||
return tr::lng_auction_round_value(
|
||||
lt_n,
|
||||
wrapped(state.currentRound),
|
||||
lt_amount,
|
||||
wrapped(state.totalRounds),
|
||||
tr::marked);
|
||||
}) | rpl::flatten_latest();
|
||||
const auto round = AddTableRow(
|
||||
raw,
|
||||
tr::lng_auction_round_label(),
|
||||
std::move(roundText));
|
||||
|
||||
auto availabilityText = state->value.value(
|
||||
) | rpl::map([](const Data::GiftAuctionState &state) {
|
||||
const auto wrapped = [](int count) {
|
||||
return rpl::single(tr::marked(Lang::FormatCountDecimal(count)));
|
||||
};
|
||||
return tr::lng_auction_availability_value(
|
||||
lt_n,
|
||||
wrapped(state.giftsLeft),
|
||||
lt_amount,
|
||||
wrapped(state.gift->limitedCount),
|
||||
tr::marked);
|
||||
}) | rpl::flatten_latest();
|
||||
AddTableRow(
|
||||
raw,
|
||||
tr::lng_auction_availability_label(),
|
||||
std::move(availabilityText));
|
||||
|
||||
const auto tooltip = std::make_shared<TableRowTooltipData>(
|
||||
TableRowTooltipData{ .parent = container });
|
||||
state->value.value(
|
||||
) | rpl::map([](const Data::GiftAuctionState &state) {
|
||||
return state.averagePrice;
|
||||
}) | rpl::filter(
|
||||
rpl::mappers::_1 != 0
|
||||
) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=](int64 price) {
|
||||
delete round;
|
||||
|
||||
raw->insertRow(
|
||||
2,
|
||||
object_ptr<FlatLabel>(
|
||||
if (preview) {
|
||||
AddTableRow(
|
||||
raw,
|
||||
tr::lng_gift_unique_availability_label(),
|
||||
rpl::single(tr::marked(
|
||||
Lang::FormatCountDecimal(now.gift->limitedCount))));
|
||||
AddTableRow(
|
||||
raw,
|
||||
tr::lng_auction_rounds_label(),
|
||||
rpl::single(tr::marked(
|
||||
Lang::FormatCountDecimal(now.totalRounds))));
|
||||
AddTableRow(
|
||||
raw,
|
||||
tr::lng_auction_rounds_first(),
|
||||
((now.roundDurationFirst % 3600)
|
||||
? tr::lng_minutes(
|
||||
lt_count,
|
||||
rpl::single(now.roundDurationFirst / 60.),
|
||||
tr::marked)
|
||||
: tr::lng_hours(
|
||||
lt_count,
|
||||
rpl::single(now.roundDurationFirst / 3600.),
|
||||
tr::marked)));
|
||||
if (now.totalRounds > 1) {
|
||||
AddTableRow(
|
||||
raw,
|
||||
tr::lng_auction_average_label(),
|
||||
raw->st().defaultLabel),
|
||||
MakeAveragePriceValue(raw, tooltip, name, price),
|
||||
st::giveawayGiftCodeLabelMargin,
|
||||
st::giveawayGiftCodeValueMargin);
|
||||
raw->resizeToWidth(raw->widthNoMargins());
|
||||
}, raw->lifetime());
|
||||
tr::lng_auction_rounds_rest(
|
||||
lt_last,
|
||||
rpl::single(QString::number(now.totalRounds))),
|
||||
((now.roundDurationRest % 3600)
|
||||
? tr::lng_auction_rounds_rest_minutes(
|
||||
lt_count,
|
||||
rpl::single(now.roundDurationRest / 60.),
|
||||
tr::marked)
|
||||
: tr::lng_auction_rounds_rest_hours(
|
||||
lt_count,
|
||||
rpl::single(now.roundDurationRest / 3600.),
|
||||
tr::marked)));
|
||||
}
|
||||
} else {
|
||||
auto roundText = state->value.value(
|
||||
) | rpl::map([](const Data::GiftAuctionState &state) {
|
||||
const auto wrapped = [](int count) {
|
||||
return rpl::single(tr::marked(Lang::FormatCountDecimal(count)));
|
||||
};
|
||||
return tr::lng_auction_round_value(
|
||||
lt_n,
|
||||
wrapped(state.currentRound),
|
||||
lt_amount,
|
||||
wrapped(state.totalRounds),
|
||||
tr::marked);
|
||||
}) | rpl::flatten_latest();
|
||||
const auto round = AddTableRow(
|
||||
raw,
|
||||
tr::lng_auction_round_label(),
|
||||
std::move(roundText));
|
||||
|
||||
auto availabilityText = state->value.value(
|
||||
) | rpl::map([](const Data::GiftAuctionState &state) {
|
||||
const auto wrapped = [](int count) {
|
||||
return rpl::single(tr::marked(Lang::FormatCountDecimal(count)));
|
||||
};
|
||||
return tr::lng_auction_availability_value(
|
||||
lt_n,
|
||||
wrapped(state.giftsLeft),
|
||||
lt_amount,
|
||||
wrapped(state.gift->limitedCount),
|
||||
tr::marked);
|
||||
}) | rpl::flatten_latest();
|
||||
AddTableRow(
|
||||
raw,
|
||||
tr::lng_auction_availability_label(),
|
||||
std::move(availabilityText));
|
||||
|
||||
const auto tooltip = std::make_shared<TableRowTooltipData>(
|
||||
TableRowTooltipData{ .parent = container });
|
||||
state->value.value(
|
||||
) | rpl::map([](const Data::GiftAuctionState &state) {
|
||||
return state.averagePrice;
|
||||
}) | rpl::filter(
|
||||
rpl::mappers::_1 != 0
|
||||
) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=](int64 price) {
|
||||
delete round;
|
||||
|
||||
raw->insertRow(
|
||||
2,
|
||||
object_ptr<FlatLabel>(
|
||||
raw,
|
||||
tr::lng_auction_average_label(),
|
||||
raw->st().defaultLabel),
|
||||
MakeAveragePriceValue(raw, tooltip, name, price),
|
||||
st::giveawayGiftCodeLabelMargin,
|
||||
st::giveawayGiftCodeValueMargin);
|
||||
raw->resizeToWidth(raw->widthNoMargins());
|
||||
}, raw->lifetime());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1092,6 +1167,93 @@ void AuctionGotGiftsBox(
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<Data::UniqueGift> MakePreviewAuctionStream(
|
||||
const Data::StarGift &info,
|
||||
rpl::producer<Data::UniqueGiftAttributes> attributes) {
|
||||
Expects(attributes);
|
||||
|
||||
auto initial = Data::UniqueGift{
|
||||
.title = info.resellTitle,
|
||||
.model = Data::UniqueGiftModel{
|
||||
.document = info.document,
|
||||
},
|
||||
.pattern = Data::UniqueGiftPattern{
|
||||
.document = info.document,
|
||||
},
|
||||
.backdrop = Data::UniqueGiftBackdrop{
|
||||
.centerColor = QColor(0x3a, 0x76, 0xb4),
|
||||
.edgeColor = QColor(0x10, 0x2d, 0x4d),
|
||||
.patternColor = QColor(0, 0, 0, 0),
|
||||
.textColor = QColor(0xff, 0xff, 0xff),
|
||||
},
|
||||
};
|
||||
return rpl::single(initial) | rpl::then(std::move(
|
||||
attributes
|
||||
) | rpl::map([=](const Data::UniqueGiftAttributes &values)
|
||||
-> rpl::producer<Data::UniqueGift> {
|
||||
if (values.backdrops.empty()
|
||||
|| values.models.empty()
|
||||
|| values.patterns.empty()) {
|
||||
return rpl::never<Data::UniqueGift>();
|
||||
}
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
struct State {
|
||||
Data::UniqueGiftAttributes data;
|
||||
std::vector<int> modelIndices;
|
||||
std::vector<int> patternIndices;
|
||||
std::vector<int> backdropIndices;
|
||||
};
|
||||
const auto state = lifetime.make_state<State>(State{
|
||||
.data = values,
|
||||
});
|
||||
|
||||
const auto put = [=] {
|
||||
const auto index = [](
|
||||
std::vector<int> &indices,
|
||||
const auto &v) {
|
||||
const auto fill = [&] {
|
||||
if (!indices.empty()) {
|
||||
return;
|
||||
}
|
||||
indices = ranges::views::ints(
|
||||
0
|
||||
) | ranges::views::take(
|
||||
v.size()
|
||||
) | ranges::to_vector;
|
||||
ranges::shuffle(indices);
|
||||
};
|
||||
fill();
|
||||
const auto result = indices.back();
|
||||
indices.pop_back();
|
||||
fill();
|
||||
if (indices.back() == result) {
|
||||
std::swap(indices.front(), indices.back());
|
||||
}
|
||||
return result;
|
||||
};
|
||||
auto &models = state->data.models;
|
||||
auto &patterns = state->data.patterns;
|
||||
auto &backdrops = state->data.backdrops;
|
||||
consumer.put_next(Data::UniqueGift{
|
||||
.title = info.resellTitle,
|
||||
.model = models[index(state->modelIndices, models)],
|
||||
.pattern = patterns[index(state->patternIndices, patterns)],
|
||||
.backdrop = backdrops[index(state->backdropIndices, backdrops)],
|
||||
});
|
||||
};
|
||||
|
||||
put();
|
||||
base::timer_each(
|
||||
kSwitchPreviewCoverInterval / 3
|
||||
) | rpl::start_with_next(put, lifetime);
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}) | rpl::flatten_latest());
|
||||
}
|
||||
|
||||
void AuctionInfoBox(
|
||||
not_null<GenericBox*> box,
|
||||
not_null<Window::SessionController*> window,
|
||||
@@ -1101,105 +1263,160 @@ void AuctionInfoBox(
|
||||
|
||||
struct State {
|
||||
explicit State(not_null<Main::Session*> session)
|
||||
: delegate(session, GiftButtonMode::Minimal) {
|
||||
: delegate(session, GiftButtonMode::Minimal) {
|
||||
}
|
||||
|
||||
Delegate delegate;
|
||||
rpl::variable<Data::GiftAuctionState> value;
|
||||
rpl::variable<int> minutesLeft;
|
||||
rpl::variable<int> minutesTillEnd;
|
||||
rpl::variable<int> secondsTillStart;
|
||||
rpl::variable<Data::UniqueGiftAttributes> attributes;
|
||||
|
||||
std::vector<Data::GiftAcquired> acquired;
|
||||
bool acquiredRequested = false;
|
||||
|
||||
base::unique_qptr<PopupMenu> menu;
|
||||
|
||||
rpl::lifetime previewLifetime;
|
||||
bool previewRequested = false;
|
||||
};
|
||||
const auto show = window->uiShow();
|
||||
const auto state = box->lifetime().make_state<State>(&show->session());
|
||||
state->value = std::move(value);
|
||||
const auto &now = state->value.current();
|
||||
|
||||
state->minutesLeft = MinutesLeftTillValue(now.endDate);
|
||||
const auto auctions = &show->session().giftAuctions();
|
||||
const auto giftId = now.gift->id;
|
||||
if (auto attributes = auctions->attributes(giftId)) {
|
||||
state->attributes = std::move(*attributes);
|
||||
} else {
|
||||
auctions->requestAttributes(giftId, crl::guard(box, [=] {
|
||||
state->attributes.force_assign(*auctions->attributes(giftId));
|
||||
}));
|
||||
}
|
||||
state->minutesTillEnd = MinutesLeftTillValue(now.endDate);
|
||||
state->secondsTillStart = SecondsLeftTillValue(now.startDate);
|
||||
const auto started = !state->secondsTillStart.current();
|
||||
const auto perRound = now.gift->auctionGiftsPerRound;
|
||||
|
||||
box->setStyle(st::giftBox);
|
||||
box->setNoContentMargin(true);
|
||||
if (!started) {
|
||||
const auto container = box->verticalLayout();
|
||||
AddUniqueGiftCover(
|
||||
container,
|
||||
MakePreviewAuctionStream(*now.gift, state->attributes.value()),
|
||||
tr::lng_gift_upgrade_about());
|
||||
|
||||
const auto name = now.gift->resellTitle;
|
||||
const auto extend = st::defaultDropdownMenu.wrap.shadow.extend;
|
||||
const auto side = st::giftBoxGiftSmall;
|
||||
const auto size = QSize(side, side).grownBy(extend);
|
||||
const auto preview = box->addRow(
|
||||
object_ptr<FixedHeightWidget>(box, size.height()),
|
||||
st::auctionInfoPreviewMargin);
|
||||
const auto gift = CreateChild<GiftButton>(preview, &state->delegate);
|
||||
gift->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
gift->setDescriptor(GiftTypeStars{
|
||||
.info = *now.gift,
|
||||
}, GiftButtonMode::Minimal);
|
||||
AddSkip(container, st::defaultVerticalListSkip * 2);
|
||||
|
||||
preview->widthValue() | rpl::start_with_next([=](int width) {
|
||||
const auto left = (width - size.width()) / 2;
|
||||
gift->setGeometry(
|
||||
QRect(QPoint(left, 0), size).marginsRemoved(extend),
|
||||
extend);
|
||||
}, gift->lifetime());
|
||||
AddUniqueCloseButton(box, {}, MakeAuctionFillMenuCallback(show, now));
|
||||
} else {
|
||||
const auto name = now.gift->resellTitle;
|
||||
const auto extend = st::defaultDropdownMenu.wrap.shadow.extend;
|
||||
const auto side = st::giftBoxGiftSmall;
|
||||
const auto size = QSize(side, side).grownBy(extend);
|
||||
const auto preview = box->addRow(
|
||||
object_ptr<FixedHeightWidget>(box, size.height()),
|
||||
st::auctionInfoPreviewMargin);
|
||||
const auto gift = CreateChild<GiftButton>(preview, &state->delegate);
|
||||
gift->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
gift->setDescriptor(GiftTypeStars{
|
||||
.info = *now.gift,
|
||||
}, GiftButtonMode::Minimal);
|
||||
|
||||
const auto rounds = state->value.current().totalRounds;
|
||||
const auto perRound = state->value.current().gift->auctionGiftsPerRound;
|
||||
auto aboutText = state->value.value(
|
||||
) | rpl::map([=](const Data::GiftAuctionState &state) {
|
||||
if (state.finished()) {
|
||||
return tr::lng_auction_text_ended(tr::now, tr::marked);
|
||||
}
|
||||
return tr::lng_auction_text(
|
||||
tr::now,
|
||||
lt_count,
|
||||
perRound,
|
||||
lt_name,
|
||||
tr::bold(name),
|
||||
lt_link,
|
||||
tr::lng_auction_text_link(
|
||||
preview->widthValue() | rpl::start_with_next([=](int width) {
|
||||
const auto left = (width - size.width()) / 2;
|
||||
gift->setGeometry(
|
||||
QRect(QPoint(left, 0), size).marginsRemoved(extend),
|
||||
extend);
|
||||
}, gift->lifetime());
|
||||
|
||||
const auto rounds = state->value.current().totalRounds;
|
||||
auto aboutText = state->value.value(
|
||||
) | rpl::map([=](const Data::GiftAuctionState &state) {
|
||||
if (state.finished()) {
|
||||
return tr::lng_auction_text_ended(tr::now, tr::marked);
|
||||
}
|
||||
return tr::lng_auction_text(
|
||||
tr::now,
|
||||
lt_arrow,
|
||||
Text::IconEmoji(&st::textMoreIconEmoji),
|
||||
tr::link),
|
||||
tr::rich);
|
||||
});
|
||||
box->addRow(
|
||||
object_ptr<FlatLabel>(
|
||||
box,
|
||||
name,
|
||||
st::uniqueGiftTitle),
|
||||
style::al_top);
|
||||
const auto about = box->addRow(
|
||||
object_ptr<FlatLabel>(
|
||||
box,
|
||||
std::move(aboutText),
|
||||
st::uniqueGiftSubtitle),
|
||||
st::boxRowPadding + QMargins(0, st::auctionInfoSubtitleSkip, 0, 0),
|
||||
style::al_top);
|
||||
about->setTryMakeSimilarLines(true);
|
||||
box->resizeToWidth(box->widthNoMargins());
|
||||
lt_count,
|
||||
perRound,
|
||||
lt_name,
|
||||
tr::bold(name),
|
||||
lt_link,
|
||||
tr::lng_auction_text_link(
|
||||
tr::now,
|
||||
lt_arrow,
|
||||
Text::IconEmoji(&st::textMoreIconEmoji),
|
||||
tr::link),
|
||||
tr::rich);
|
||||
});
|
||||
box->addRow(
|
||||
object_ptr<FlatLabel>(
|
||||
box,
|
||||
name,
|
||||
st::uniqueGiftTitle),
|
||||
style::al_top);
|
||||
const auto about = box->addRow(
|
||||
object_ptr<FlatLabel>(
|
||||
box,
|
||||
std::move(aboutText),
|
||||
st::uniqueGiftSubtitle),
|
||||
(st::boxRowPadding
|
||||
+ QMargins(0, st::auctionInfoSubtitleSkip, 0, 0)),
|
||||
style::al_top);
|
||||
about->setTryMakeSimilarLines(true);
|
||||
|
||||
about->setClickHandlerFilter([=](const auto &...) {
|
||||
show->show(Box(AuctionAboutBox, rounds, perRound, nullptr));
|
||||
return false;
|
||||
});
|
||||
about->setClickHandlerFilter([=](const auto &...) {
|
||||
show->show(Box(AuctionAboutBox, rounds, perRound, nullptr));
|
||||
return false;
|
||||
});
|
||||
|
||||
const auto close = CreateChild<IconButton>(
|
||||
box->verticalLayout(),
|
||||
st::boxTitleClose);
|
||||
close->setClickedCallback([=] { box->closeBox(); });
|
||||
|
||||
const auto menu = CreateChild<IconButton>(
|
||||
box->verticalLayout(),
|
||||
st::boxTitleMenu);
|
||||
menu->setClickedCallback(MakeAuctionMenuCallback(menu, show, now));
|
||||
const auto weakMenu = base::make_weak(menu);
|
||||
|
||||
box->verticalLayout()->widthValue() | rpl::start_with_next([=](int) {
|
||||
close->moveToRight(0, 0);
|
||||
if (const auto strong = weakMenu.get()) {
|
||||
strong->moveToRight(close->width(), 0);
|
||||
}
|
||||
}, close->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
state->value.value(),
|
||||
state->minutesTillEnd.value()
|
||||
) | rpl::start_with_next([=](
|
||||
const Data::GiftAuctionState &state,
|
||||
int minutes) {
|
||||
const auto finished = state.finished() || (minutes <= 0);
|
||||
about->setTextColorOverride(finished
|
||||
? st::attentionButtonFg->c
|
||||
: std::optional<QColor>());
|
||||
if (const auto strong = finished ? weakMenu.get() : nullptr) {
|
||||
delete strong;
|
||||
}
|
||||
}, box->lifetime());
|
||||
}
|
||||
|
||||
box->addRow(
|
||||
AuctionInfoTable(box, box->verticalLayout(), state->value.value()),
|
||||
st::boxRowPadding + st::auctionInfoTableMargin);
|
||||
|
||||
state->value.value(
|
||||
) | rpl::map([=](const Data::GiftAuctionState &value) {
|
||||
return value.my.gotCount;
|
||||
}) | rpl::filter(
|
||||
rpl::mappers::_1 > 0
|
||||
) | rpl::take(1) | rpl::start_with_next([=](int count) {
|
||||
if (const auto got = now.my.gotCount) {
|
||||
box->addRow(
|
||||
object_ptr<FlatLabel>(
|
||||
box,
|
||||
tr::lng_auction_bought(
|
||||
lt_count_decimal,
|
||||
rpl::single(count * 1.),
|
||||
rpl::single(1. * got),
|
||||
lt_emoji,
|
||||
rpl::single(Data::SingleCustomEmoji(
|
||||
state->value.current().gift->document)),
|
||||
@@ -1224,7 +1441,7 @@ void AuctionInfoBox(
|
||||
state->acquired));
|
||||
} else if (!state->acquiredRequested) {
|
||||
state->acquiredRequested = true;
|
||||
show->session().giftAuctions().requestAcquired(
|
||||
auctions->requestAcquired(
|
||||
value.gift->id,
|
||||
crl::guard(box, [=](
|
||||
std::vector<Data::GiftAcquired> result) {
|
||||
@@ -1241,11 +1458,56 @@ void AuctionInfoBox(
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}, box->lifetime());
|
||||
|
||||
} else if (const auto variants = now.gift->upgradeVariants) {
|
||||
using namespace Data;
|
||||
state->attributes.value(
|
||||
) | rpl::filter([](const UniqueGiftAttributes &list) {
|
||||
return !list.models.empty();
|
||||
}) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=](const UniqueGiftAttributes &list) {
|
||||
auto emoji = tr::marked();
|
||||
const auto indices = RandomIndicesSubset(list.models.size(), 3);
|
||||
for (const auto index : indices) {
|
||||
emoji.append(Data::SingleCustomEmoji(
|
||||
list.models[index].document));
|
||||
}
|
||||
box->addRow(
|
||||
object_ptr<FlatLabel>(
|
||||
box,
|
||||
tr::lng_auction_preview_variants(
|
||||
lt_count_decimal,
|
||||
rpl::single(1. * variants),
|
||||
lt_emoji,
|
||||
rpl::single(emoji),
|
||||
lt_arrow,
|
||||
rpl::single(Text::IconEmoji(&st::textMoreIconEmoji)),
|
||||
tr::link),
|
||||
st::uniqueGiftValueAvailableLink,
|
||||
st::defaultPopupMenu,
|
||||
Core::TextContext({ .session = &show->session() })),
|
||||
st::boxRowPadding + st::uniqueGiftValueAvailableMargin,
|
||||
style::al_top
|
||||
)->setClickHandlerFilter([=](const auto &...) {
|
||||
//if (state->previewRequested) {
|
||||
// return false;
|
||||
//}
|
||||
//state->previewRequested = true;
|
||||
//const auto &value = state->value.current();
|
||||
//const auto &gift = *value.gift;
|
||||
//state->previewLifetime = ShowStarGiftResale(
|
||||
// window,
|
||||
// peer,
|
||||
// gift.id,
|
||||
// gift.resellTitle,
|
||||
// [=] { state->previewRequested = false; });
|
||||
return false;
|
||||
});
|
||||
}, box->lifetime());
|
||||
}
|
||||
const auto button = box->addButton(rpl::single(QString()), [=] {
|
||||
if (state->value.current().finished()
|
||||
|| !state->minutesLeft.current()) {
|
||||
|| !state->minutesTillEnd.current()) {
|
||||
box->closeBox();
|
||||
return;
|
||||
}
|
||||
@@ -1266,40 +1528,6 @@ void AuctionInfoBox(
|
||||
button,
|
||||
AuctionButtonCountdownType::Join,
|
||||
state->value.value());
|
||||
|
||||
box->setNoContentMargin(true);
|
||||
const auto close = CreateChild<IconButton>(
|
||||
box->verticalLayout(),
|
||||
st::boxTitleClose);
|
||||
close->setClickedCallback([=] { box->closeBox(); });
|
||||
|
||||
const auto menu = CreateChild<IconButton>(
|
||||
box->verticalLayout(),
|
||||
st::boxTitleMenu);
|
||||
menu->setClickedCallback(MakeAuctionMenuCallback(menu, show, now));
|
||||
const auto weakMenu = base::make_weak(menu);
|
||||
|
||||
box->verticalLayout()->widthValue() | rpl::start_with_next([=](int) {
|
||||
close->moveToRight(0, 0);
|
||||
if (const auto strong = weakMenu.get()) {
|
||||
strong->moveToRight(close->width(), 0);
|
||||
}
|
||||
}, close->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
state->value.value(),
|
||||
state->minutesLeft.value()
|
||||
) | rpl::start_with_next([=](
|
||||
const Data::GiftAuctionState &state,
|
||||
int minutes) {
|
||||
const auto finished = state.finished() || (minutes <= 0);
|
||||
about->setTextColorOverride(finished
|
||||
? st::attentionButtonFg->c
|
||||
: std::optional<QColor>());
|
||||
if (const auto strong = finished ? weakMenu.get() : nullptr) {
|
||||
delete strong;
|
||||
}
|
||||
}, box->lifetime());
|
||||
}
|
||||
|
||||
base::weak_qptr<BoxContent> ChooseAndShowAuctionBox(
|
||||
@@ -1308,16 +1536,21 @@ base::weak_qptr<BoxContent> ChooseAndShowAuctionBox(
|
||||
std::shared_ptr<rpl::variable<Data::GiftAuctionState>> state,
|
||||
Fn<void()> boxClosed) {
|
||||
const auto local = &peer->session().local();
|
||||
const auto &now = state->current();
|
||||
const auto finished = now.finished()
|
||||
|| (now.endDate <= base::unixtime::now());
|
||||
const auto showBidBox = now.my.bid
|
||||
const auto ¤t = state->current();
|
||||
const auto now = base::unixtime::now();
|
||||
const auto started = (current.startDate <= now);
|
||||
const auto finished = current.finished() || (current.endDate <= now);
|
||||
const auto showBidBox = current.my.bid
|
||||
&& !finished
|
||||
&& (!now.my.to || now.my.to == peer);
|
||||
const auto showChangeRecipient = !showBidBox && now.my.bid && !finished;
|
||||
&& (!current.my.to || current.my.to == peer);
|
||||
const auto showChangeRecipient = !showBidBox
|
||||
&& current.my.bid
|
||||
&& !finished;
|
||||
const auto showInfoBox = !showBidBox
|
||||
&& !showChangeRecipient
|
||||
&& (local->readPref<bool>(kAuctionAboutShownPref) || finished);
|
||||
&& (!started
|
||||
|| finished
|
||||
|| local->readPref<bool>(kAuctionAboutShownPref));
|
||||
auto box = base::weak_qptr<BoxContent>();
|
||||
if (showBidBox) {
|
||||
box = window->show(MakeAuctionBidBox({
|
||||
@@ -1333,13 +1566,13 @@ base::weak_qptr<BoxContent> ChooseAndShowAuctionBox(
|
||||
peer,
|
||||
nullptr,
|
||||
Info::PeerGifts::GiftTypeStars{
|
||||
.info = *now.gift,
|
||||
.info = *current.gift,
|
||||
},
|
||||
state->value()));
|
||||
sendBox->boxClosing(
|
||||
) | rpl::start_with_next(close, sendBox->lifetime());
|
||||
};
|
||||
const auto from = now.my.to;
|
||||
const auto from = current.my.to;
|
||||
const auto text = (from->isSelf()
|
||||
? tr::lng_auction_change_already_me(tr::now, tr::rich)
|
||||
: tr::lng_auction_change_already(
|
||||
@@ -1383,8 +1616,8 @@ base::weak_qptr<BoxContent> ChooseAndShowAuctionBox(
|
||||
};
|
||||
box = window->show(Box(
|
||||
AuctionAboutBox,
|
||||
now.totalRounds,
|
||||
now.gift->auctionGiftsPerRound,
|
||||
current.totalRounds,
|
||||
current.gift->auctionGiftsPerRound,
|
||||
understood));
|
||||
}
|
||||
if (const auto strong = box.get()) {
|
||||
@@ -1457,35 +1690,60 @@ void SetAuctionButtonCountdownText(
|
||||
rpl::producer<Data::GiftAuctionState> value) {
|
||||
struct State {
|
||||
rpl::variable<Data::GiftAuctionState> value;
|
||||
rpl::variable<int> minutesLeft;
|
||||
rpl::variable<int> minutesTillEnd;
|
||||
rpl::variable<int> secondsTillStart;
|
||||
};
|
||||
const auto state = button->lifetime().make_state<State>();
|
||||
state->value = std::move(value);
|
||||
state->minutesLeft = MinutesLeftTillValue(
|
||||
state->value.current().endDate);
|
||||
|
||||
const auto &now = state->value.current();
|
||||
const auto preview = (now.startDate > base::unixtime::now());
|
||||
if (preview) {
|
||||
state->secondsTillStart = SecondsLeftTillValue(now.startDate);
|
||||
} else {
|
||||
state->minutesTillEnd = MinutesLeftTillValue(now.endDate);
|
||||
}
|
||||
|
||||
auto buttonTitle = rpl::combine(
|
||||
state->value.value(),
|
||||
state->minutesLeft.value()
|
||||
) | rpl::map([=](const Data::GiftAuctionState &state, int minutes) {
|
||||
return (state.finished() || minutes <= 0)
|
||||
(preview
|
||||
? state->secondsTillStart.value()
|
||||
: state->minutesTillEnd.value())
|
||||
) | rpl::map([=](const Data::GiftAuctionState &state, int leftTill) {
|
||||
return (state.finished() || (!preview && leftTill <= 0))
|
||||
? tr::lng_box_ok(tr::marked)
|
||||
: (type == AuctionButtonCountdownType::Join)
|
||||
? tr::lng_auction_join_button(tr::marked)
|
||||
: tr::lng_auction_join_bid(tr::marked);
|
||||
: (type == AuctionButtonCountdownType::Place)
|
||||
? tr::lng_auction_join_bid(tr::marked)
|
||||
: tr::lng_auction_join_button(tr::marked);
|
||||
}) | rpl::flatten_latest();
|
||||
|
||||
auto buttonSubtitle = rpl::combine(
|
||||
state->value.value(),
|
||||
state->minutesLeft.value()
|
||||
(preview
|
||||
? state->secondsTillStart.value()
|
||||
: state->minutesTillEnd.value())
|
||||
) | rpl::map([=](
|
||||
const Data::GiftAuctionState &state,
|
||||
int minutes) -> rpl::producer<TextWithEntities> {
|
||||
if (state.finished() || minutes <= 0) {
|
||||
const Data::GiftAuctionState &state,
|
||||
int leftTill
|
||||
) -> rpl::producer<TextWithEntities> {
|
||||
if (state.finished() || leftTill <= 0) {
|
||||
return rpl::single(TextWithEntities());
|
||||
} else if (preview) {
|
||||
const auto hours = (leftTill / 3600);
|
||||
const auto minutes = (leftTill % 3600) / 60;
|
||||
const auto seconds = (leftTill % 60);
|
||||
const auto time = hours
|
||||
? u"%1:%2:%3"_q
|
||||
.arg(hours).arg(minutes, 2, 10, QChar('0'))
|
||||
.arg(seconds, 2, 10, QChar('0'))
|
||||
: u"%1:%2"_q.arg(minutes).arg(seconds, 2, 10, QChar('0'));
|
||||
return tr::lng_auction_join_starts_in(
|
||||
lt_time,
|
||||
rpl::single(tr::marked(time)),
|
||||
tr::marked);
|
||||
}
|
||||
const auto hours = (minutes / 60);
|
||||
minutes -= (hours * 60);
|
||||
const auto hours = (leftTill / 60);
|
||||
const auto minutes = leftTill % 60;
|
||||
|
||||
auto value = [](int count) {
|
||||
return rpl::single(tr::marked(QString::number(count)));
|
||||
|
||||
@@ -14,6 +14,7 @@ class Show;
|
||||
namespace Data {
|
||||
struct GiftAuctionState;
|
||||
struct ActiveAuctions;
|
||||
struct StarGift;
|
||||
} // namespace Data
|
||||
|
||||
namespace Info::PeerGifts {
|
||||
@@ -49,6 +50,7 @@ struct AuctionBidBoxArgs {
|
||||
enum class AuctionButtonCountdownType {
|
||||
Join,
|
||||
Place,
|
||||
Preview,
|
||||
};
|
||||
void SetAuctionButtonCountdownText(
|
||||
not_null<RoundButton*> button,
|
||||
|
||||
@@ -3007,15 +3007,16 @@ void AddUniqueGiftCover(
|
||||
}
|
||||
p.drawImage(0, 0, gift.gradient);
|
||||
|
||||
Ui::PaintBgPoints(
|
||||
p,
|
||||
Ui::PatternBgPoints(),
|
||||
gift.emojis,
|
||||
gift.emoji.get(),
|
||||
*gift.gift,
|
||||
QRect(0, 0, width, pointsHeight),
|
||||
shown);
|
||||
|
||||
if (gift.gift->backdrop.patternColor.alpha() > 0) {
|
||||
Ui::PaintBgPoints(
|
||||
p,
|
||||
Ui::PatternBgPoints(),
|
||||
gift.emojis,
|
||||
gift.emoji.get(),
|
||||
*gift.gift,
|
||||
QRect(0, 0, width, pointsHeight),
|
||||
shown);
|
||||
}
|
||||
const auto lottie = gift.lottie.get();
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto request = Lottie::FrameRequest{
|
||||
|
||||
@@ -112,6 +112,7 @@ void GiftAuctions::requestAcquired(
|
||||
.date = data.vdate().v,
|
||||
.bidAmount = int64(data.vbid_amount().v),
|
||||
.round = data.vround().v,
|
||||
.number = data.vgift_num().value_or_empty(),
|
||||
.position = data.vpos().v,
|
||||
.nameHidden = data.is_name_hidden(),
|
||||
});
|
||||
@@ -129,6 +130,49 @@ void GiftAuctions::requestAcquired(
|
||||
}).send();
|
||||
}
|
||||
|
||||
std::optional<Data::UniqueGiftAttributes> GiftAuctions::attributes(
|
||||
uint64 giftId) const {
|
||||
const auto i = _attributes.find(giftId);
|
||||
return (i != end(_attributes) && i->second.waiters.empty())
|
||||
? i->second.lists
|
||||
: std::optional<Data::UniqueGiftAttributes>();
|
||||
}
|
||||
|
||||
void GiftAuctions::requestAttributes(uint64 giftId, Fn<void()> ready) {
|
||||
auto &entry = _attributes[giftId];
|
||||
entry.waiters.push_back(std::move(ready));
|
||||
if (entry.waiters.size() > 1) {
|
||||
return;
|
||||
}
|
||||
_session->api().request(MTPpayments_GetStarGiftUpgradeAttributes(
|
||||
MTP_long(giftId)
|
||||
)).done([=](const MTPpayments_StarGiftUpgradeAttributes &result) {
|
||||
const auto &attributes = result.data().vattributes().v;
|
||||
auto &entry = _attributes[giftId];
|
||||
auto &info = entry.lists;
|
||||
info.models.reserve(attributes.size());
|
||||
info.patterns.reserve(attributes.size());
|
||||
info.backdrops.reserve(attributes.size());
|
||||
for (const auto &attribute : attributes) {
|
||||
attribute.match([&](const MTPDstarGiftAttributeModel &data) {
|
||||
info.models.push_back(Api::FromTL(_session, data));
|
||||
}, [&](const MTPDstarGiftAttributePattern &data) {
|
||||
info.patterns.push_back(Api::FromTL(_session, data));
|
||||
}, [&](const MTPDstarGiftAttributeBackdrop &data) {
|
||||
info.backdrops.push_back(Api::FromTL(data));
|
||||
}, [](const MTPDstarGiftAttributeOriginalDetails &data) {
|
||||
});
|
||||
}
|
||||
for (const auto &ready : base::take(entry.waiters)) {
|
||||
ready();
|
||||
}
|
||||
}).fail([=] {
|
||||
for (const auto &ready : base::take(_attributes[giftId].waiters)) {
|
||||
ready();
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
rpl::producer<ActiveAuctions> GiftAuctions::active() const {
|
||||
return _activeChanged.events_starting_with_copy(
|
||||
rpl::empty
|
||||
@@ -233,6 +277,7 @@ void GiftAuctions::requestActive() {
|
||||
result.match([=](const MTPDpayments_starGiftActiveAuctions &data) {
|
||||
const auto owner = &_session->data();
|
||||
owner->processUsers(data.vusers());
|
||||
owner->processChats(data.vchats());
|
||||
|
||||
auto giftsFound = base::flat_set<QString>();
|
||||
const auto &list = data.vauctions().v;
|
||||
@@ -294,6 +339,7 @@ void GiftAuctions::request(const QString &slug) {
|
||||
const auto &data = result.data();
|
||||
|
||||
_session->data().processUsers(data.vusers());
|
||||
_session->data().processChats(data.vchats());
|
||||
|
||||
raw->state.gift = Api::FromTL(_session, data.vgift());
|
||||
if (!raw->state.gift) {
|
||||
@@ -367,6 +413,7 @@ void GiftAuctions::apply(
|
||||
entry->giftsLeft = data.vgifts_left().v;
|
||||
entry->currentRound = data.vcurrent_round().v;
|
||||
entry->totalRounds = data.vtotal_rounds().v;
|
||||
data.vrounds().v;
|
||||
entry->averagePrice = 0;
|
||||
}, [&](const MTPDstarGiftAuctionStateFinished &data) {
|
||||
entry->averagePrice = data.vaverage_price().v;
|
||||
|
||||
@@ -42,6 +42,8 @@ struct GiftAuctionState {
|
||||
TimeId startDate = 0;
|
||||
TimeId endDate = 0;
|
||||
TimeId nextRoundAt = 0;
|
||||
TimeId roundDurationFirst = 0;
|
||||
TimeId roundDurationRest = 0;
|
||||
int currentRound = 0;
|
||||
int totalRounds = 0;
|
||||
int giftsLeft = 0;
|
||||
@@ -58,6 +60,7 @@ struct GiftAcquired {
|
||||
TimeId date = 0;
|
||||
int64 bidAmount = 0;
|
||||
int round = 0;
|
||||
int number = 0;
|
||||
int position = 0;
|
||||
bool nameHidden = false;
|
||||
};
|
||||
@@ -80,6 +83,10 @@ public:
|
||||
uint64 giftId,
|
||||
Fn<void(std::vector<Data::GiftAcquired>)> done);
|
||||
|
||||
[[nodiscard]] std::optional<Data::UniqueGiftAttributes> attributes(
|
||||
uint64 giftId) const;
|
||||
void requestAttributes(uint64 giftId, Fn<void()> ready);
|
||||
|
||||
[[nodiscard]] rpl::producer<ActiveAuctions> active() const;
|
||||
[[nodiscard]] rpl::producer<bool> hasActiveChanges() const;
|
||||
[[nodiscard]] bool hasActive() const;
|
||||
@@ -100,6 +107,10 @@ private:
|
||||
}
|
||||
friend inline bool operator==(MyStateKey, MyStateKey) = default;
|
||||
};
|
||||
struct Attributes {
|
||||
Data::UniqueGiftAttributes lists;
|
||||
std::vector<Fn<void()>> waiters;
|
||||
};
|
||||
|
||||
void request(const QString &slug);
|
||||
Entry *find(uint64 giftId) const;
|
||||
@@ -126,6 +137,7 @@ private:
|
||||
|
||||
base::Timer _timer;
|
||||
base::flat_map<QString, std::unique_ptr<Entry>> _map;
|
||||
base::flat_map<uint64, Attributes> _attributes;
|
||||
|
||||
rpl::event_stream<> _activeChanged;
|
||||
mtpRequestId _activeRequestId = 0;
|
||||
|
||||
@@ -162,6 +162,7 @@ struct GiftCode {
|
||||
int starsUpgradedBySender = 0;
|
||||
int starsForDetailsRemove = 0;
|
||||
int starsBid = 0;
|
||||
int giftNum = 0;
|
||||
int limitedCount = 0;
|
||||
int limitedLeft = 0;
|
||||
int64 count = 0;
|
||||
|
||||
@@ -38,6 +38,12 @@ struct UniqueGiftBackdrop : UniqueGiftAttribute {
|
||||
int id = 0;
|
||||
};
|
||||
|
||||
struct UniqueGiftAttributes {
|
||||
std::vector<UniqueGiftModel> models;
|
||||
std::vector<UniqueGiftBackdrop> backdrops;
|
||||
std::vector<UniqueGiftPattern> patterns;
|
||||
};
|
||||
|
||||
struct UniqueGiftOriginalDetails {
|
||||
PeerId senderId = 0;
|
||||
PeerId recipientId = 0;
|
||||
@@ -48,6 +54,7 @@ struct UniqueGiftOriginalDetails {
|
||||
struct UniqueGiftValue {
|
||||
QString currency;
|
||||
int64 valuePrice = 0;
|
||||
int64 valuePriceUsd = 0;
|
||||
CreditsAmount initialPriceStars;
|
||||
int64 initialSalePrice = 0;
|
||||
TimeId initialSaleDate = 0;
|
||||
@@ -130,10 +137,12 @@ struct StarGift {
|
||||
int resellCount = 0;
|
||||
QString auctionSlug;
|
||||
int auctionGiftsPerRound = 0;
|
||||
TimeId auctionStartDate = 0;
|
||||
int limitedLeft = 0;
|
||||
int limitedCount = 0;
|
||||
int perUserTotal = 0;
|
||||
int perUserRemains = 0;
|
||||
int upgradeVariants = 0;
|
||||
TimeId firstSaleDate = 0;
|
||||
TimeId lastSaleDate = 0;
|
||||
TimeId lockedUntilDate = 0;
|
||||
@@ -214,6 +223,7 @@ struct SavedStarGift {
|
||||
QString giftPrepayUpgradeHash;
|
||||
PeerId fromId = 0;
|
||||
TimeId date = 0;
|
||||
int giftNum = 0;
|
||||
bool upgradeSeparate = false;
|
||||
bool upgradable = false;
|
||||
bool anonymous = false;
|
||||
|
||||
@@ -6807,6 +6807,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
|
||||
.starsUpgradedBySender = int(
|
||||
data.vupgrade_stars().value_or_empty()),
|
||||
.starsBid = bid,
|
||||
.giftNum = data.vgift_num().value_or_empty(),
|
||||
.type = Data::GiftType::StarGift,
|
||||
.upgradeSeparate = data.is_upgrade_separate(),
|
||||
.upgradeGifted = data.is_prepaid_upgrade(),
|
||||
|
||||
@@ -238,7 +238,8 @@ constexpr auto kSponsoredUserpicLines = 2;
|
||||
: (type == WebPageType::GiftCollection)
|
||||
? tr::lng_view_button_collection(tr::now)
|
||||
: (type == WebPageType::Auction)
|
||||
? (page->auction && page->auction->endDate
|
||||
? (page->auction
|
||||
&& page->auction->endDate
|
||||
&& page->auction->endDate <= base::unixtime::now())
|
||||
? tr::lng_auction_preview_view_results(tr::now)
|
||||
: tr::lng_auction_preview_join(tr::now)
|
||||
|
||||
@@ -205,6 +205,11 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
|
||||
: Lang::FormatCountDecimal(number);
|
||||
};
|
||||
|
||||
const auto auctionStartDate = v::is<GiftTypeStars>(descriptor)
|
||||
? v::get<GiftTypeStars>(descriptor).info.auctionStartDate
|
||||
: TimeId();
|
||||
const auto upcomingAuction = (auctionStartDate > base::unixtime::now());
|
||||
|
||||
_descriptor = descriptor;
|
||||
_resalePrice = resalePrice;
|
||||
const auto resale = (_resalePrice > 0);
|
||||
@@ -276,7 +281,7 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
|
||||
: unique
|
||||
? tr::lng_gift_transfer_button(tr::now, tr::marked)
|
||||
: data.info.auction()
|
||||
? (data.info.soldOut
|
||||
? ((data.info.soldOut || upcomingAuction)
|
||||
? tr::lng_gift_stars_auction_view
|
||||
: tr::lng_gift_stars_auction_join)(tr::now, tr::marked)
|
||||
: _delegate->star().append(' ' + format(data.info.stars))),
|
||||
@@ -789,6 +794,9 @@ void GiftButton::paintEvent(QPaintEvent *e) {
|
||||
}, [&](const GiftTypeStars &data) {
|
||||
const auto count = data.info.limitedCount;
|
||||
const auto pinned = data.pinned || data.pinnedSelection;
|
||||
const auto now = base::unixtime::now();
|
||||
const auto upcomingAuction = (data.info.auctionStartDate > 0)
|
||||
&& (data.info.auctionStartDate > now);
|
||||
if (count || pinned) {
|
||||
const auto yourLeft = data.info.perUserTotal
|
||||
? (data.info.perUserRemains
|
||||
@@ -808,7 +816,9 @@ void GiftButton::paintEvent(QPaintEvent *e) {
|
||||
: soldOut
|
||||
? tr::lng_gift_stars_sold_out(tr::now)
|
||||
: (!unique && data.info.auction())
|
||||
? tr::lng_gift_stars_auction(tr::now)
|
||||
? (upcomingAuction
|
||||
? tr::lng_gift_stars_auction_soon
|
||||
: tr::lng_gift_stars_auction)(tr::now)
|
||||
: (!data.userpic
|
||||
&& !data.info.unique
|
||||
&& data.info.requirePremium)
|
||||
|
||||
Reference in New Issue
Block a user