Implement upcoming auction preview box.

This commit is contained in:
John Preston
2025-12-02 14:39:43 +04:00
parent b08cf75f0b
commit 65b3a36984
11 changed files with 247 additions and 93 deletions
+9 -6
View File
@@ -4086,12 +4086,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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_rounds_exact" = "Round {n}";
"lng_auction_rounds_range" = "Rounds {n}-{last}";
"lng_auction_rounds_seconds#one" = "{count} minute each";
"lng_auction_rounds_seconds#other" = "{count} minutes each";
"lng_auction_rounds_minutes#one" = "{count} minute each";
"lng_auction_rounds_minutes#other" = "{count} minutes each";
"lng_auction_rounds_hours#one" = "{count} hour each";
"lng_auction_rounds_hours#other" = "{count} hours each";
"lng_auction_rounds_extended" = "{duration} + {increase} for late bids in top {n}";
"lng_auction_end_label" = "Ends";
"lng_auction_round_label" = "Current Round";
"lng_auction_round_value" = "{n} of {amount}";
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer_rpl.h"
#include "base/unixtime.h"
#include "boxes/peers/replace_boost_box.h"
#include "boxes/premium_preview_box.h"
#include "boxes/send_credits_box.h" // CreditsEmojiSmall
#include "boxes/share_box.h"
#include "boxes/star_gift_box.h"
@@ -991,33 +992,55 @@ void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
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(
const auto formatDuration = [&](TimeId value, bool exact) {
return (!(value % 3600))
? (exact ? tr::lng_hours : tr::lng_auction_rounds_hours)(
tr::now,
lt_count,
rpl::single(now.roundDurationFirst / 60.),
tr::marked)
: tr::lng_hours(
value / 3600)
: (!(value % 60))
? (exact ? tr::lng_minutes : tr::lng_auction_rounds_minutes)(
tr::now,
lt_count,
rpl::single(now.roundDurationFirst / 3600.),
tr::marked)));
if (now.totalRounds > 1) {
value / 60)
: (exact ? tr::lng_seconds : tr::lng_auction_rounds_seconds)(
tr::now,
lt_count,
value);
};
for (auto i = 0, n = int(now.roundParameters.size()); i != n; ++i) {
const auto &that = now.roundParameters[i];
const auto next = (i + 1 < n)
? now.roundParameters[i + 1]
: Data::GiftAuctionRound{ now.totalRounds + 1 };
const auto exact = (next.number == that.number + 1);
const auto extended = that.extendTop && that.extendDuration;
const auto duration = formatDuration(that.duration, exact);
const auto value = extended
? tr::lng_auction_rounds_extended(
tr::now,
lt_duration,
duration,
lt_increase,
formatDuration(that.extendDuration, true),
lt_n,
QString::number(that.extendTop))
: duration;
AddTableRow(
raw,
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)));
(exact
? tr::lng_auction_rounds_exact(
lt_n,
rpl::single(QString::number(that.number)))
: tr::lng_auction_rounds_range(
lt_n,
rpl::single(QString::number(that.number)),
lt_last,
rpl::single(QString::number(next.number - 1)))),
object_ptr<FlatLabel>(
raw,
value,
st::auctionInfoValueMultiline));
}
} else {
auto roundText = state->value.value(
@@ -1180,12 +1203,9 @@ void AuctionGotGiftsBox(
.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),
},
.backdrop = (info.background
? info.background->backdrop()
: Data::UniqueGiftBackdrop()),
};
return rpl::single(initial) | rpl::then(std::move(
attributes
@@ -1302,11 +1322,22 @@ void AuctionInfoBox(
box->setNoContentMargin(true);
if (!started) {
const auto container = box->verticalLayout();
AddUniqueGiftCover(
container,
MakePreviewAuctionStream(*now.gift, state->attributes.value()),
tr::lng_gift_upgrade_about());
auto gift = MakePreviewAuctionStream(
*now.gift,
state->attributes.value());
AddUniqueGiftCover(container, std::move(gift), {
.pretitle = tr::lng_auction_preview_name(),
.subtitle = tr::lng_auction_preview_learn_gifts(
lt_arrow,
rpl::single(Text::IconEmoji(&st::textMoreIconEmoji)),
tr::link),
.subtitleClick = [=] {
ShowPremiumPreviewBox(
window,
PremiumFeature::AnimatedEmoji); AssertIsDebug();
},
.subtitleLinkColored = true,
});
AddSkip(container, st::defaultVerticalListSkip * 2);
AddUniqueCloseButton(box, {}, MakeAuctionFillMenuCallback(show, now));
@@ -1712,9 +1743,11 @@ void SetAuctionButtonCountdownText(
) | rpl::map([=](const Data::GiftAuctionState &state, int leftTill) {
return (state.finished() || (!preview && leftTill <= 0))
? tr::lng_box_ok(tr::marked)
: (type == AuctionButtonCountdownType::Place)
? tr::lng_auction_join_bid(tr::marked)
: tr::lng_auction_join_button(tr::marked);
: (type != AuctionButtonCountdownType::Place)
? tr::lng_auction_join_button(tr::marked)
: preview
? tr::lng_auction_join_early_bid(tr::marked)
: tr::lng_auction_join_bid(tr::marked);
}) | rpl::flatten_latest();
auto buttonSubtitle = rpl::combine(
+94 -29
View File
@@ -137,6 +137,7 @@ constexpr auto kCrossfadeDuration = crl::time(400);
constexpr auto kUpgradeDoneToastDuration = 4 * crl::time(1000);
constexpr auto kGiftsPreloadTimeout = 3 * crl::time(1000);
constexpr auto kResellPriceCacheLifetime = 60 * crl::time(1000);
constexpr auto kGradientButtonBgOpacity = 0.6;
using namespace HistoryView;
using namespace Info::PeerGifts;
@@ -2798,38 +2799,73 @@ void SetupResalePriceButton(
void AddUniqueGiftCover(
not_null<VerticalLayout*> container,
rpl::producer<Data::UniqueGift> data,
rpl::producer<QString> subtitleOverride,
rpl::producer<CreditsAmount> resalePrice,
Fn<void()> resaleClick) {
UniqueGiftCoverArgs &&args) {
const auto cover = container->add(object_ptr<RpWidget>(container));
struct Released {
Released() : white(QColor(255, 255, 255)) {
Released() : link(QColor(255, 255, 255)) {
}
rpl::variable<TextWithEntities> subtitleText;
style::owned_color white;
std::optional<Ui::Premium::ColoredMiniStars> stars;
style::owned_color link;
style::FlatLabel st;
PeerData *by = nullptr;
QColor bg;
};
const auto released = cover->lifetime().make_state<Released>();
released->st = st::uniqueGiftReleasedBy;
released->st.palette.linkFg = released->white.color();
released->st.palette.linkFg = released->link.color();
if (resalePrice) {
if (args.resalePrice) {
auto background = rpl::duplicate(
data
) | rpl::map([=](const Data::UniqueGift &unique) {
return unique.backdrop.patternColor;
auto result = unique.backdrop.patternColor;
result.setAlphaF(kGradientButtonBgOpacity * result.alphaF());
return result;
});
SetupResalePriceButton(
cover,
std::move(background),
std::move(resalePrice),
std::move(resaleClick));
std::move(args.resalePrice),
std::move(args.resaleClick));
}
const auto pretitle = args.pretitle
? CreateChild<FlatLabel>(
cover,
std::move(args.pretitle),
st::uniqueGiftPretitle)
: nullptr;
if (pretitle) {
released->stars.emplace(
cover,
true,
Ui::Premium::MiniStarsType::SlowStars);
const auto white = QColor(255, 255, 255);
released->stars->setColorOverride(QGradientStops{
{ 0., anim::with_alpha(white, .3) },
{ 1., white },
});
pretitle->geometryValue() | rpl::start_with_next([=](QRect rect) {
const auto half = rect.height() / 2;
released->stars->setCenter(rect - QMargins(half, 0, half, 0));
}, pretitle->lifetime());
pretitle->setAttribute(Qt::WA_TransparentForMouseEvents);
pretitle->setTextColorOverride(QColor(255, 255, 255));
pretitle->paintOn([=](QPainter &p) {
auto hq = PainterHighQualityEnabler(p);
const auto radius = pretitle->height() / 2.;
p.setPen(Qt::NoPen);
auto bg = released->bg;
bg.setAlphaF(kGradientButtonBgOpacity * bg.alphaF());
p.setBrush(bg);
p.drawRoundedRect(pretitle->rect(), radius, radius);
p.translate(-pretitle->pos());
released->stars->paint(p);
});
}
const auto title = CreateChild<FlatLabel>(
cover,
rpl::duplicate(
@@ -2837,13 +2873,10 @@ void AddUniqueGiftCover(
) | rpl::map([](const Data::UniqueGift &now) { return now.title; }),
st::uniqueGiftTitle);
title->setTextColorOverride(QColor(255, 255, 255));
released->subtitleText = subtitleOverride
? std::move(
subtitleOverride
) | Ui::Text::ToWithEntities() | rpl::type_erased()
released->subtitleText = args.subtitle
? std::move(args.subtitle)
: rpl::duplicate(data) | rpl::map([=](const Data::UniqueGift &gift) {
released->by = gift.releasedBy;
released->bg = gift.backdrop.patternColor;
return gift.releasedBy
? tr::lng_gift_unique_number_by(
tr::now,
@@ -2860,13 +2893,18 @@ void AddUniqueGiftCover(
});
if (!released->by) {
released->st = st::uniqueGiftSubtitle;
released->st.palette.linkFg = released->white.color();
released->st.palette.linkFg = released->link.color();
}
const auto subtitle = CreateChild<FlatLabel>(
cover,
released->subtitleText.value(),
released->st);
if (released->by) {
if (const auto handler = args.subtitleClick) {
subtitle->setClickHandlerFilter([=](const auto &...) {
handler();
return false;
});
} else if (released->by) {
const auto button = CreateChild<AbstractButton>(cover);
subtitle->raise();
subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
@@ -2912,15 +2950,28 @@ void AddUniqueGiftCover(
};
const auto state = cover->lifetime().make_state<State>();
const auto lottieSize = st::creditsHistoryEntryStarGiftSize;
const auto updateLinkFg = args.subtitleLinkColored;
const auto updateColors = [=](float64 progress) {
subtitle->setTextColorOverride((progress == 0.)
const auto color = (progress == 0.)
? state->now.gift->backdrop.textColor
: (progress == 1.)
? state->next.gift->backdrop.textColor
: anim::color(
state->now.gift->backdrop.textColor,
state->next.gift->backdrop.textColor,
progress));
progress);
if (updateLinkFg) {
released->link.update(color);
}
subtitle->setTextColorOverride(color);
released->bg = (progress == 0.)
? state->now.gift->backdrop.patternColor
: (progress == 1.)
? state->next.gift->backdrop.patternColor
: anim::color(
state->now.gift->backdrop.patternColor,
state->next.gift->backdrop.patternColor,
progress);
};
std::move(
data
@@ -2974,10 +3025,24 @@ void AddUniqueGiftCover(
}
const auto available = width - 2 * skip;
title->resizeToWidth(available);
title->moveToLeft(skip, st::uniqueGiftTitleTop);
subtitle->resizeToWidth(available);
subtitle->moveToLeft(skip, st::uniqueGiftSubtitleTop);
auto top = st::uniqueGiftTitleTop;
if (pretitle) {
pretitle->move((width - pretitle->width()) / 2, top);
top += pretitle->height()
+ (st::uniqueGiftSubtitleTop - st::uniqueGiftTitleTop)
- title->height();
}
title->moveToLeft(skip, top);
if (pretitle) {
top += title->height() + st::defaultVerticalListSkip;
} else {
top += st::uniqueGiftSubtitleTop - st::uniqueGiftTitleTop;
}
subtitle->moveToLeft(skip, top);
cover->resize(width, subtitle->y() + subtitle->height() + skip);
}, cover->lifetime());
@@ -3007,7 +3072,7 @@ void AddUniqueGiftCover(
}
p.drawImage(0, 0, gift.gradient);
if (gift.gift->backdrop.patternColor.alpha() > 0) {
if (gift.gift->pattern.document != gift.gift->model.document) {
Ui::PaintBgPoints(
p,
Ui::PatternBgPoints(),
@@ -3803,16 +3868,16 @@ struct UpgradeArgs : StarGiftUpgradeArgs {
void AddUpgradeGiftCover(
not_null<VerticalLayout*> container,
const UpgradeArgs &args) {
AddUniqueGiftCover(
container,
MakeUpgradeGiftStream(args),
(args.savedId
? tr::lng_gift_upgrade_about()
AddUniqueGiftCover(container, MakeUpgradeGiftStream(args), {
.subtitle = (args.savedId
? tr::lng_gift_upgrade_about(tr::marked)
: (args.peer->isBroadcast()
? tr::lng_gift_upgrade_preview_about_channel
: tr::lng_gift_upgrade_preview_about)(
lt_name,
rpl::single(args.peer->shortName()))));
rpl::single(tr::marked(args.peer->shortName())),
tr::marked)),
});
}
class UpgradePriceValue final {
+10 -3
View File
@@ -65,12 +65,19 @@ void ShowStarGiftBox(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer);
struct UniqueGiftCoverArgs {
rpl::producer<QString> pretitle;
rpl::producer<TextWithEntities> subtitle;
Fn<void()> subtitleClick;
bool subtitleLinkColored = false;
rpl::producer<CreditsAmount> resalePrice;
Fn<void()> resaleClick;
};
void AddUniqueGiftCover(
not_null<VerticalLayout*> container,
rpl::producer<Data::UniqueGift> data,
rpl::producer<QString> subtitleOverride = nullptr,
rpl::producer<CreditsAmount> resalePrice = nullptr,
Fn<void()> resaleClick = nullptr);
UniqueGiftCoverArgs &&args);
void AddWearGiftCover(
not_null<VerticalLayout*> container,
const Data::UniqueGift &data,
@@ -413,7 +413,24 @@ void GiftAuctions::apply(
entry->giftsLeft = data.vgifts_left().v;
entry->currentRound = data.vcurrent_round().v;
entry->totalRounds = data.vtotal_rounds().v;
data.vrounds().v;
const auto &rounds = data.vrounds().v;
entry->roundParameters.clear();
entry->roundParameters.reserve(rounds.size());
for (const auto &round : rounds) {
round.match([&](const MTPDstarGiftAuctionRound &data) {
entry->roundParameters.push_back({
.number = data.vnum().v,
.duration = data.vduration().v,
});
}, [&](const MTPDstarGiftAuctionRoundExtendable &data) {
entry->roundParameters.push_back({
.number = data.vnum().v,
.duration = data.vduration().v,
.extendTop = data.vextend_top().v,
.extendDuration = data.vextend_window().v,
});
});
}
entry->averagePrice = 0;
}, [&](const MTPDstarGiftAuctionStateFinished &data) {
entry->averagePrice = data.vaverage_price().v;
@@ -31,19 +31,25 @@ struct StarGiftAuctionMyState {
bool returned = false;
};
struct GiftAuctionRound {
int number = 0;
TimeId duration = 0;
int extendTop = 0;
TimeId extendDuration = 0;
};
struct GiftAuctionState {
std::optional<StarGift> gift;
StarGiftAuctionMyState my;
std::vector<GiftAuctionBidLevel> bidLevels;
std::vector<not_null<UserData*>> topBidders;
std::vector<GiftAuctionRound> roundParameters;
crl::time subscribedTill = 0;
int64 minBidAmount = 0;
int64 averagePrice = 0;
TimeId startDate = 0;
TimeId endDate = 0;
TimeId nextRoundAt = 0;
TimeId roundDurationFirst = 0;
TimeId roundDurationRest = 0;
int currentRound = 0;
int totalRounds = 0;
int giftsLeft = 0;
@@ -591,6 +591,7 @@ auto AuctionBg(
not_null<Element*> view,
Data::UniqueGiftBackdrop backdrop,
std::shared_ptr<Data::StarGift> gift,
TimeId startDate,
TimeId endDate)
-> Fn<void(
Painter&,
@@ -676,22 +677,31 @@ auto AuctionBg(
}
const auto now = base::unixtime::now();
const auto startsIn = std::max(startDate - now, 0);
const auto left = std::max(endDate - now, 0);
if (left > 0) {
if (startsIn > 0 || left > 0) {
if (!state->timer) {
state->timer = std::make_unique<base::Timer>([=] {
view->repaint();
});
}
state->timer->callOnce(1000);
} else if (left <= 0 && state->timer) {
} else if (state->timer) {
state->timer = nullptr;
}
const auto text = left > 0
? QString("%1:%2:%3")
.arg(left / 3600, 2, 10, QChar('0'))
.arg((left % 3600) / 60, 2, 10, QChar('0'))
.arg(left % 60, 2, 10, QChar('0'))
const auto still = (startsIn > 0) ? startsIn : left;
const auto time = (still >= 3600)
? u"%1:%2:%3"_q
.arg(still / 3600)
.arg((still % 3600) / 60, 2, 10, QChar('0'))
.arg(still % 60, 2, 10, QChar('0'))
: u"%1:%2"_q
.arg(still / 60)
.arg(still % 60, 2, 10, QChar('0'));
const auto text = (startsIn > 0)
? tr::lng_auction_join_starts_in(tr::now, lt_time, time)
: (left > 0)
? time
: tr::lng_auctino_preview_finished(tr::now);
const auto &font = st::webPageAuctionTimeFont;
@@ -66,6 +66,7 @@ class MediaGenericPart;
not_null<Element*> view,
Data::UniqueGiftBackdrop backdrop,
std::shared_ptr<Data::StarGift> gift,
TimeId startDate,
TimeId endDate)
-> Fn<void(
Painter&,
@@ -238,11 +238,16 @@ constexpr auto kSponsoredUserpicLines = 2;
: (type == WebPageType::GiftCollection)
? tr::lng_view_button_collection(tr::now)
: (type == WebPageType::Auction)
? (page->auction
? ((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)
: (page->auction
&& page->auction->auctionGift->auctionStartDate
&& (page->auction->auctionGift->auctionStartDate
> base::unixtime::now()))
? tr::lng_auction_bar_view(tr::now)
: tr::lng_auction_preview_join(tr::now))
: QString());
if (page->iv) {
return Ui::Text::IconEmoji(&st::historyIvIcon).append(text);
@@ -548,6 +553,7 @@ QSize WebPage::countOptimalSize() {
_parent,
backdrop,
gift,
_data->auction->auctionGift->auctionStartDate,
_data->auction->endDate);
},
});
@@ -1342,12 +1342,10 @@ void GenericCreditsEntryBox(
style);
};
const auto canResell = CanResellGift(session, e);
AddUniqueGiftCover(
content,
rpl::single(*uniqueGift),
{},
std::move(price),
canResell ? std::move(change) : Fn<void()>());
AddUniqueGiftCover(content, rpl::single(*uniqueGift), {
.resalePrice = std::move(price),
.resaleClick = canResell ? std::move(change) : Fn<void()>(),
});
AddSkip(content, st::defaultVerticalListSkip * 2);
@@ -277,6 +277,10 @@ creditsHistoryEntriesList: PeerList(defaultPeerList) {
subscriptionCreditsBadgePadding: margins(10px, 1px, 8px, 3px);
uniqueGiftModelTop: 20px;
uniqueGiftPretitle: FlatLabel(defaultFlatLabel) {
style: semiboldTextStyle;
margin: margins(12px, 4px, 12px, 4px);
}
uniqueGiftTitle: FlatLabel(boxTitle) {
align: align(top);
}
@@ -451,6 +455,10 @@ videoStreamStarsCover: PremiumCover(creditsLowBalancePremiumCover) {
auctionInfoPreviewMargin: margins(0px, 24px, 0px, 8px);
auctionInfoSubtitleSkip: 8px;
auctionInfoTableMargin: margins(0px, 12px, 0px, 12px);
auctionInfoValueMultiline: FlatLabel(defaultTableValue) {
minWidth: 96px;
maxHeight: 96px;
}
auctionBidEmoji: IconEmoji {
icon: icon {{ "settings/button_auction", windowFg }};
padding: margins(-4px, -2px, -4px, 0px);