From 147dbee05120aea17c6fc8a2734633594ae7260b Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 21 Nov 2025 18:58:03 +0400 Subject: [PATCH] Implement active auctions chats list bar. --- .../icons/settings/button_auction.png | Bin 0 -> 522 bytes .../icons/settings/button_auction@2x.png | Bin 0 -> 935 bytes .../icons/settings/button_auction@3x.png | Bin 0 -> 1439 bytes .../icons/settings/toast_auction.png | Bin 0 -> 605 bytes .../icons/settings/toast_auction@2x.png | Bin 0 -> 1170 bytes .../icons/settings/toast_auction@3x.png | Bin 0 -> 1757 bytes .../boxes/star_gift_auction_box.cpp | 319 +++++++++++++++++- .../SourceFiles/boxes/star_gift_auction_box.h | 15 + .../data/components/gift_auctions.cpp | 250 +++++++++++--- .../data/components/gift_auctions.h | 38 ++- Telegram/SourceFiles/dialogs/dialogs.style | 5 + .../dialogs/dialogs_top_bar_suggestion.cpp | 58 +++- .../ui/dialogs_top_bar_suggestion_content.cpp | 37 +- .../ui/dialogs_top_bar_suggestion_content.h | 8 +- Telegram/SourceFiles/ui/effects/credits.style | 25 ++ Telegram/lib_ui | 2 +- 16 files changed, 698 insertions(+), 59 deletions(-) create mode 100644 Telegram/Resources/icons/settings/button_auction.png create mode 100644 Telegram/Resources/icons/settings/button_auction@2x.png create mode 100644 Telegram/Resources/icons/settings/button_auction@3x.png create mode 100644 Telegram/Resources/icons/settings/toast_auction.png create mode 100644 Telegram/Resources/icons/settings/toast_auction@2x.png create mode 100644 Telegram/Resources/icons/settings/toast_auction@3x.png diff --git a/Telegram/Resources/icons/settings/button_auction.png b/Telegram/Resources/icons/settings/button_auction.png new file mode 100644 index 0000000000000000000000000000000000000000..6fbaa5e019ad929734115e03dba274698c92c38c GIT binary patch literal 522 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/toast_auction.png b/Telegram/Resources/icons/settings/toast_auction.png new file mode 100644 index 0000000000000000000000000000000000000000..41b34d5fe1d14326b30b6c3784aca0c0476c596e GIT binary patch literal 605 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgf?6{|kV~B;| z-O1kFje!!!*&k23edz9~rih4LZv2jlkce1&EkUV6tB#$qU%vD1I}a5l zvF@8v!aG>!ykHP(+;B7J_17qgpVwxeZ4=sOy_or31p=fS?83{1OV`D|HJ?Q literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/toast_auction@2x.png b/Telegram/Resources/icons/settings/toast_auction@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e03d6a987f98a94fc6173cd0a68459355c5f1c8a GIT binary patch literal 1170 zcmV;D1a13?P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NF{7FPXR9Fe^nARUMn?WPQ$Rq#?(Qzr22*)?IaD(;G9Dft*t}$%o}NZTMEoF=pP%3M_BK&{ zeSIw}D}!oQR@UR=Be@HIXJ_XhVe<9$-Q3(HdeZRa<)yf|7^*orIZsbd#3wT|)AT^z z-rno$>qLnkK0iMf6cj)>KR+Kfe=9C7&cr~To}R0#tGtFm8ytCgdC)E@Dq?4RczD>v zG+M28d3o6&fjEi%!Z`y$aBy&AV`FJ)sjaQ8iGlbEDiV+xeCa|$LR1mR@Y>ngb#!#h z&CQ{&2n!3t(mOjl3I#qsK5A=gmHR7NwzIR7eQ0FcsHiB7Mx!K1@9*z5H8qMfR5>j! zE;1k($hJD24ov<1{ro(!5u3WYItaPCx~h_scODxXBVs2fC&;$g%7K9aa!VS5roO%& zOSiSPu_P(4q|gK1jF@eYkB`H{!@)E(G(?y{)6md>#amihq+2XWDpZtr%gxOdvn`rK zoN;@5d*l_K3Eyq3*xcOAPqHK_FQ(7~IhSNx=p7s!gocJXI5>=qj7aiLO-)!3m4T;` zY?dSyD$2Vd+amZxX@7qoX~xmfv9+}oNl#h@2YG&eo_J!2FlT3H%PT4OfFuqnQ9>t# zeuymM=H|Arus}R9#BnrnW!3BTA`|MGlamu+!aEvC84@3wr1bQ3;{2^)MMVV)0{QeM zB_-mb9v&V`OH0HPLloN7)Kt;`>oZkVRY=6Vl0hH{pf6ZiS>Zh;8%=j|a`M*+#3!q( zt67}T7>QfP9`W+>T3cHanecLA;gvki&m(`#%!T8R7hD$?m(kJDqoX5H z-2f0iM^8^rVqzldx%hyQ8Pt}ksVP)@v?xa0RNtDInBaF>t&L_^wWRR}#3HJS30 kXERpA8Ubqrep>|o0Y#Tj3aw;FP5=M^07*qoM6N<$g8E_k&Hw-a literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings/toast_auction@3x.png b/Telegram/Resources/icons/settings/toast_auction@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..8bf2db0ab2df9dd825a7aded2f0f5bf2088a6a48 GIT binary patch literal 1757 zcmV<31|s>1P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS>M@d9MRA>e5T3bj}O%%?wEN_@; z8a@bW5=e?6B8XzBM4@=ah$Os1(1Re-gW`iX6igpPBk@5nN<~q;qrW1UprV8rnAcDw z6tCD_Q~N*vfnhhZ&v@*defEFgoCjI6X4YEYH+%NXthGIN&h#_QfN2IyGhmtlwPzqL zEp2LQ>dTielarHiadB$H(Np}Al9Jzl{z^+r_2gSUH*Rij2L}g#@;Yf17ZM3bMNua5{S`G}8?*F+5Zyu3V>aOoV@+1dHqw{HS&IXO8x2T{qv)2B}b#L)HZ z*)x@(=^VDOuu#k`D=SOqASyZFH+_a`W@e^J(9Rh4;>C-}$;po&KfZM75-}|+D`Uto zO)T0o1~K^X%a<K0bc(p($&Cbq_ zlarG{fD40ISXi{Swo+IEp{1n-gW(L%t*)+4F!Ir(N5YuOcLHd0bF%;<)78|}WNvN_ zT<6c9udlCXdTCSTQyUN)8yhJaErmC4-Vkp1`}*~3S*!5yaOEG0h6tE)O|WFKN|mo( zy@EL$%TCSk@UVuM%B#f-NyDhN138Hj(^ym#-OUeKTHvuFKbonRud@IHL_u)n`g%x~Ym zeO5pG_<#QU@p?mO?%cVvva&*+#`^mDg9i^t)LXZ1egFRb@17E|!YvthWkYb_V|aLY z{QUWoK~IwlY{%qvX9j4~92y$3va;eM8c0)9Q^TW_CKq?_-sPF|CX5DKxwW;mfjG#9 z85tR2Fp(y>rKhJ8ll%AYuQmAL!Jf`by=Qc_X~f~cq{ z*{UdSJ32aQYikQDS6A1?#YGgbLPJ9pr@;q^jEv;LQ4Nv{Wd6h|CMM=oZsX(Q(}7mU_P|sninr#P$s+pZuAb0+UT!ezxXjkgU=E6_V$2_HaBu)hB#!Ujzo%A=u(!7t5D=g^872TOA4oJ9DAFdl z#m2@GtE8kPEK$-sJw5H{=t%TMMMY8*Uf$i^_4oH@LMz?`H^C+el-N9?qoWB9EY6P~ zKlb$WNYi^yPtUcrHJ*=DgNlHkpP%Anm;lI>k=RRdFftA_Y^02pw0ZO9P29^EE2g=< zz3uDkE44C&j8Y5=G7J=H6BiNjg&p_ljg1Yd2`_JMZsIO#2tLvn;D$mPkA`Y+f%Nh5 zk(w|vKm$4*FDrmNrhqY2yeY090_drUJr*05oGc}(#G=L0Rh$eHKvTt+z``bZYD$7l z0$T;8AuC|B@$&LwLMz@xjUFEyWC7tQqs4jw4s7n&;$=Nh<)=@du$>B=DAI+;GB`L$ zK~UlC+qYWA!=a3$nHtIptE;Q-?(SMT)Y~xRiwrkp)@M3rahbxEN|q8;E-fvQWJhnf zwQ+>AJ3BjjVq#)>c^M_)8#iufV*#6uk~2X#2JOx0~G-=G6j2awLQ~^Nx|04 zcU4tY;RB6==mo9|3JQ2Ydeo@)&nXKF3wor%yFv|EFs(Xvi53GkA4OLSl1r4|bR?k3 z{N$Ujrk|p$5ENxRO`&zyi(KRA&^>^_!`9Z8pjXE((HBj8WWaykk%AeX9{)pZUI~ph zdektG0qBU{WNw-P(+rqqz%&D<88FR&am~QLpkRd)4I(AT00000NkvXXu0mjf(#sSn literal 0 HcmV?d00001 diff --git a/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp b/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp index 469e5462f5..24bb92a77a 100644 --- a/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp @@ -8,16 +8,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/star_gift_auction_box.h" #include "api/api_text_entities.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 "calls/group/calls_group_common.h" +#include "core/application.h" #include "core/credits_amount.h" #include "core/ui_integration.h" #include "data/components/credits.h" #include "data/components/gift_auctions.h" +#include "data/stickers/data_custom_emoji.h" +#include "data/data_document.h" #include "data/data_message_reactions.h" #include "data/data_session.h" #include "data/data_user.h" @@ -180,6 +184,17 @@ struct BidSliderValues { }; } +[[nodiscard]] QString NiceCountdownText(int seconds) { + const auto minutes = seconds / 60; + const auto hours = minutes / 60; + return hours + ? u"%1:%2:%3"_q + .arg(hours) + .arg((minutes % 60), 2, 10, QChar('0')) + .arg((seconds % 60), 2, 10, QChar('0')) + : u"%1:%2"_q.arg(minutes).arg((seconds % 60), 2, 10, QChar('0')); +} + [[nodiscard]] object_ptr MakeBidRow( not_null parent, std::shared_ptr show, @@ -380,17 +395,11 @@ object_ptr MakeAuctionInfoBlocks( auto untilTitle = rpl::duplicate( stateValue ) | rpl::map([=](const Data::GiftAuctionState &state) { - return SecondsLeftTillValue(state.nextRoundAt); - }) | rpl::flatten_latest() | rpl::map([=](int seconds) { - const auto minutes = seconds / 60; - const auto hours = minutes / 60; - return hours - ? u"%1:%2:%3"_q - .arg(hours) - .arg((minutes % 60), 2, 10, QChar('0')) - .arg((seconds % 60), 2, 10, QChar('0')) - : u"%1:%2"_q.arg(minutes).arg((seconds % 60), 2, 10, QChar('0')); - }) | Text::ToWithEntities(); + return SecondsLeftTillValue(state.nextRoundAt + ? state.nextRoundAt + : state.endDate); + }) | rpl::flatten_latest( + ) | rpl::map(NiceCountdownText) | rpl::map(tr::marked); auto leftTitle = rpl::duplicate( stateValue ) | rpl::map([=](const Data::GiftAuctionState &state) { @@ -653,7 +662,10 @@ void AuctionBidBox(not_null box, AuctionBidBoxArgs &&args) { box->closeBox(); if (const auto window = show->resolveWindow()) { - window->showPeer(update.to, ShowAtTheEndMsgId); + window->showPeerHistory( + update.to, + Window::SectionShow::Way::ClearStack, + ShowAtTheEndMsgId); } } }, box->lifetime()); @@ -801,6 +813,8 @@ void AuctionBidBox(not_null box, AuctionBidBoxArgs &&args) { lt_count, perRound, tr::rich), + .st = &st::auctionBidToast, + .attach = RectPart::Top, .duration = kBidPlacedToastDuration, }); } @@ -1561,4 +1575,285 @@ void AuctionAboutBox( ).append(' ').append(tr::lng_auction_about_understood(tr::now)))); } +TextWithEntities ActiveAuctionsTitle(const Data::ActiveAuctions &auctions) { + const auto &list = auctions.list; + if (list.size() == 1) { + const auto auction = list.front(); + return Data::SingleCustomEmoji( + auction->gift->document + ).append(' ').append(tr::lng_auction_bar_active(tr::now)); + } + auto result = tr::marked(); + for (const auto auction : list | ranges::views::take(3)) { + result.append(Data::SingleCustomEmoji(auction->gift->document)); + } + return result.append(' ').append( + tr::lng_auction_bar_active_many(tr::now, lt_count, list.size())); +} + +ManyAuctionsState ActiveAuctionsState(const Data::ActiveAuctions &auctions) { + const auto &list = auctions.list; + const auto winning = [](not_null auction) { + const auto position = MyAuctionPosition(*auction); + return (position <= auction->gift->auctionGiftsPerRound) + ? position + : 0; + }; + PROFILE_LOG(("GOT: POSITION %1, PERROUND: %2, WINNING: %3" + ).arg(MyAuctionPosition(*list.front()) + ).arg(list.front()->gift->auctionGiftsPerRound + ).arg(winning(list.front()))); + if (list.size() == 1) { + const auto auction = list.front(); + const auto position = winning(auction); + auto text = position + ? tr::lng_auction_bar_winning( + tr::now, + lt_count, + position, + tr::marked) + : tr::lng_auction_bar_outbid(tr::now, tr::marked); + return { std::move(text), !position }; + } + auto outbid = 0; + for (const auto auction : list) { + if (!winning(auction)) { + ++outbid; + } + } + auto text = (outbid == list.size()) + ? tr::lng_auction_bar_outbid_all(tr::now, tr::marked) + : outbid + ? tr::lng_auction_bar_outbid_some( + tr::now, + lt_count, + outbid, + tr::marked) + : tr::lng_auction_bar_winning_all(tr::now, tr::marked); + return { std::move(text), outbid != 0 }; +} + +rpl::producer ActiveAuctionsButton( + const Data::ActiveAuctions &auctions) { + const auto &list = auctions.list; + const auto withIcon = [](const QString &text) { + using namespace Ui::Text; + return IconEmoji(&st::auctionBidEmoji).append(' ').append(text); + }; + if (list.size() == 1) { + const auto auction = auctions.list.front(); + const auto end = auction->nextRoundAt + ? auction->nextRoundAt + : auction->endDate; + return SecondsLeftTillValue(end) + | rpl::map(NiceCountdownText) + | rpl::map(withIcon); + } + return tr::lng_auction_bar_view() | rpl::map(withIcon); +} + +struct Single { + QString slug; + not_null document; + int round = 0; + int total = 0; + int bid = 0; + int position = 0; + int winning = 0; + TimeId ends = 0; +}; + +object_ptr MakeActiveAuctionRow( + not_null parent, + not_null window, + not_null document, + const QString &slug, + rpl::producer value) { + auto result = object_ptr(parent); + const auto raw = result.data(); + + raw->add(object_ptr(raw)); + + auto title = rpl::duplicate(value) | rpl::map([=](const Single &fields) { + return tr::lng_auction_bar_round( + tr::now, + lt_n, + QString::number(fields.round + 1), + lt_amount, + QString::number(fields.total)); + }); + raw->add( + object_ptr( + raw, + std::move(title), + st::auctionListTitle), + st::auctionListTitlePadding); + + const auto tag = Data::CustomEmojiSizeTag::Isolated; + const auto sticker = std::shared_ptr( + document->owner().customEmojiManager().create( + document, + [=] { raw->update(); }, + tag)); + + raw->paintRequest( + ) | rpl::start_with_next([=] { + auto q = QPainter(raw); + sticker->paint(q, { + .textColor = st::windowFg->c, + .now = crl::now(), + .position = QPoint(), + }); + }, raw->lifetime()); + + auto helper = Ui::Text::CustomEmojiHelper(); + const auto star = helper.paletteDependent(Ui::Earn::IconCreditsEmoji()); + auto text = rpl::duplicate(value) | rpl::map([=](const Single &fields) { + const auto stars = tr::marked(star).append(' ').append( + Lang::FormatCountDecimal(fields.bid)); + const auto outbid = (fields.position > fields.winning); + return outbid + ? tr::lng_auction_bar_bid_outbid( + tr::now, + lt_stars, + stars, + tr::rich) + : tr::lng_auction_bar_bid_ranked( + tr::now, + lt_stars, + stars, + lt_n, + tr::marked(QString::number(fields.position)), + tr::rich); + }); + const auto subtitle = raw->add( + object_ptr( + raw, + std::move(text), + st::auctionListText, + st::defaultPopupMenu, + helper.context()), + st::auctionListTextPadding); + rpl::duplicate(value) | rpl::start_with_next([=](const Single &fields) { + const auto outbid = (fields.position > fields.winning); + subtitle->setTextColorOverride(outbid + ? st::attentionButtonFg->c + : std::optional()); + }, subtitle->lifetime()); + + const auto button = raw->add( + object_ptr( + raw, + rpl::single(QString()), + st::auctionListRaise), + st::auctionListRaisePadding); + button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); + + auto secondsLeft = rpl::duplicate( + value + ) | rpl::map([=](const Single &fields) { + return SecondsLeftTillValue(fields.ends); + }) | rpl::flatten_latest(); + button->setText(rpl::combine( + std::move(secondsLeft), + tr::lng_auction_bar_raise_bid() + ) | rpl::map([=](int seconds, const QString &text) { + return Ui::Text::IconEmoji( + &st::auctionBidEmoji + ).append(' ').append(text).append(' ').append( + Ui::Text::Colorized(NiceCountdownText(seconds))); + })); + button->setClickedCallback([=] { + window->showStarGiftAuction(slug); + }); + button->setFullRadius(true); + raw->widthValue() | rpl::start_with_next([=](int width) { + button->setFullWidth(width); + }, button->lifetime()); + + return result; +} + +Fn ActiveAuctionsCallback( + not_null window, + const Data::ActiveAuctions &auctions) { + const auto &list = auctions.list; + const auto count = int(list.size()); + if (count == 1) { + const auto slug = list.front()->gift->auctionSlug; + return [=] { + window->showStarGiftAuction(slug); + }; + } + struct Auctions { + std::vector> list; + }; + const auto state = std::make_shared(); + const auto singleFrom = [](const Data::GiftAuctionState &state) { + return Single{ + .slug = state.gift->auctionSlug, + .document = state.gift->document, + .round = state.currentRound, + .total = state.totalRounds, + .bid = int(state.my.bid), + .position = MyAuctionPosition(state), + .winning = state.gift->auctionGiftsPerRound, + .ends = state.nextRoundAt ? state.nextRoundAt : state.endDate, + }; + }; + for (const auto auction : list) { + state->list.push_back(singleFrom(*auction)); + } + return [=] { + window->show(Box([=](not_null box) { + const auto rows = box->lifetime().make_state< + rpl::variable + >(count); + + box->setWidth(st::boxWideWidth); + box->setTitle(tr::lng_auction_bar_active_many( + lt_count, + rows->value() | tr::to_count())); + + const auto auctions = &window->session().giftAuctions(); + for (auto &entry : state->list) { + using Data::GiftAuctionState; + + const auto &now = entry.current(); + entry = auctions->state( + now.slug + ) | rpl::filter([=](const GiftAuctionState &state) { + return state.my.bid != 0; + }) | rpl::map(singleFrom); + + const auto skip = st::auctionListEntrySkip; + const auto row = box->addRow( + MakeActiveAuctionRow( + box, + window, + now.document, + now.slug, + entry.value()), + st::boxRowPadding + QMargins(0, skip, 0, skip)); + + auctions->state( + now.slug + ) | rpl::start_with_next([=](const GiftAuctionState &state) { + if (!state.my.bid) { + delete row; + if (const auto now = rows->current(); now > 1) { + *rows = (now - 1); + } else { + box->closeBox(); + } + } + }, row->lifetime()); + } + + box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); }); + box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); }); + })); + }; +} + } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/star_gift_auction_box.h b/Telegram/SourceFiles/boxes/star_gift_auction_box.h index eae0b32d8d..e79d1e394e 100644 --- a/Telegram/SourceFiles/boxes/star_gift_auction_box.h +++ b/Telegram/SourceFiles/boxes/star_gift_auction_box.h @@ -13,6 +13,7 @@ class Show; namespace Data { struct GiftAuctionState; +struct ActiveAuctions; } // namespace Data namespace Info::PeerGifts { @@ -60,4 +61,18 @@ void AuctionAboutBox( int giftsPerRound, Fn close)> understood); +[[nodiscard]] TextWithEntities ActiveAuctionsTitle( + const Data::ActiveAuctions &auctions); +struct ManyAuctionsState { + TextWithEntities text; + bool someOutbid = false; +}; +[[nodiscard]] ManyAuctionsState ActiveAuctionsState( + const Data::ActiveAuctions &auctions); +[[nodiscard]] rpl::producer ActiveAuctionsButton( + const Data::ActiveAuctions &auctions); +[[nodiscard]] Fn ActiveAuctionsCallback( + not_null window, + const Data::ActiveAuctions &auctions); + } // namespace Ui diff --git a/Telegram/SourceFiles/data/components/gift_auctions.cpp b/Telegram/SourceFiles/data/components/gift_auctions.cpp index 350975f999..11ac5a2d3c 100644 --- a/Telegram/SourceFiles/data/components/gift_auctions.cpp +++ b/Telegram/SourceFiles/data/components/gift_auctions.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/components/gift_auctions.h" +#include "api/api_hash.h" #include "api/api_premium.h" #include "api/api_text_entities.h" #include "apiwrap.h" @@ -18,6 +19,16 @@ namespace Data { GiftAuctions::GiftAuctions(not_null session) : _session(session) , _timer([=] { checkSubscriptions(); }) { + crl::on_main(_session, [=] { + rpl::merge( + _session->data().chatsListChanges(), + _session->data().chatsListLoadedEvents() + ) | rpl::filter( + !rpl::mappers::_1 + ) | rpl::take(1) | rpl::start_with_next([=] { + requestActive(); + }, _lifetime); + }); } GiftAuctions::~GiftAuctions() = default; @@ -50,15 +61,27 @@ rpl::producer GiftAuctions::state(const QString &slug) { void GiftAuctions::apply(const MTPDupdateStarGiftAuctionState &data) { if (const auto entry = find(data.vgift_id().v)) { + const auto was = myStateKey(entry->state); apply(entry, data.vstate()); entry->changes.fire({}); + if (was != myStateKey(entry->state)) { + _activeChanged.fire({}); + } + } else { + requestActive(); } } void GiftAuctions::apply(const MTPDupdateStarGiftAuctionUserState &data) { if (const auto entry = find(data.vgift_id().v)) { + const auto was = myStateKey(entry->state); apply(entry, data.vuser_state()); entry->changes.fire({}); + if (was != myStateKey(entry->state)) { + _activeChanged.fire({}); + } + } else { + requestActive(); } } @@ -106,6 +129,37 @@ void GiftAuctions::requestAcquired( }).send(); } +rpl::producer GiftAuctions::active() const { + return _activeChanged.events_starting_with_copy( + rpl::empty + ) | rpl::map([=] { + return collectActive(); + }); +} + +rpl::producer GiftAuctions::hasActiveChanges() const { + const auto has = hasActive(); + return _activeChanged.events( + ) | rpl::map([=] { + return hasActive(); + }) | rpl::combine_previous( + has + ) | rpl::filter([=](bool previous, bool current) { + return previous != current; + }) | rpl::map([=](bool previous, bool current) { + return current; + }); +} + +bool GiftAuctions::hasActive() const { + for (const auto &[slug, entry] : _map) { + if (myStateKey(entry->state)) { + return true; + } + } + return false; +} + void GiftAuctions::checkSubscriptions() { const auto now = crl::now(); auto next = crl::time(); @@ -128,6 +182,101 @@ void GiftAuctions::checkSubscriptions() { } } +auto GiftAuctions::myStateKey(const GiftAuctionState &state) const +-> MyStateKey { + if (!state.my.bid) { + return {}; + } + auto min = 0; + for (const auto &level : state.bidLevels) { + if (level.position > state.gift->auctionGiftsPerRound) { + break; + } else if (!min || min > level.amount) { + min = level.amount; + } + } + return { + .bid = int(state.my.bid), + .position = MyAuctionPosition(state), + .version = state.version, + }; +} + +ActiveAuctions GiftAuctions::collectActive() const { + auto result = ActiveAuctions(); + result.list.reserve(_map.size()); + for (const auto &[slug, entry] : _map) { + const auto raw = &entry->state; + if (raw->gift && raw->my.date) { + result.list.push_back(raw); + } + } + return result; +} + +uint64 GiftAuctions::countActiveHash() const { + auto result = Api::HashInit(); + for (const auto &active : collectActive().list) { + Api::HashUpdate(result, active->version); + Api::HashUpdate(result, active->my.date); + } + return Api::HashFinalize(result); +} + +void GiftAuctions::requestActive() { + if (_activeRequestId) { + return; + } + _activeRequestId = _session->api().request( + MTPpayments_GetStarGiftActiveAuctions(MTP_long(countActiveHash())) + ).done([=](const MTPpayments_StarGiftActiveAuctions &result) { + result.match([=](const MTPDpayments_starGiftActiveAuctions &data) { + const auto owner = &_session->data(); + owner->processUsers(data.vusers()); + + auto giftsFound = base::flat_set(); + const auto &list = data.vauctions().v; + giftsFound.reserve(list.size()); + for (const auto &auction : list) { + const auto &data = auction.data(); + auto gift = Api::FromTL(_session, data.vgift()); + const auto slug = gift ? gift->auctionSlug : QString(); + if (slug.isEmpty()) { + LOG(("Api Error: Bad auction gift.")); + continue; + } + auto &entry = _map[slug]; + if (!entry) { + entry = std::make_unique(); + } + const auto raw = entry.get(); + if (!raw->state.gift) { + raw->state.gift = std::move(gift); + } + apply(raw, data.vstate()); + apply(raw, data.vuser_state()); + giftsFound.emplace(slug); + } + for (const auto &[slug, entry] : _map) { + const auto my = &entry->state.my; + if (my->date && !giftsFound.contains(slug)) { + my->to = nullptr; + my->minBidAmount = 0; + my->bid = 0; + my->date = 0; + my->returned = false; + giftsFound.emplace(slug); + } + } + for (const auto &slug : giftsFound) { + _map[slug]->changes.fire({}); + } + _activeChanged.fire({}); + }, [](const MTPDpayments_starGiftActiveAuctionsNotModified &) { + }); + }).send(); +} + void GiftAuctions::request(const QString &slug) { auto &entry = _map[slug]; Assert(entry != nullptr); @@ -144,6 +293,8 @@ void GiftAuctions::request(const QString &slug) { raw->requested = false; const auto &data = result.data(); + _session->data().processUsers(data.vusers()); + raw->state.gift = Api::FromTL(_session, data.vgift()); if (!raw->state.gift) { return; @@ -152,8 +303,7 @@ void GiftAuctions::request(const QString &slug) { const auto ms = timeout * crl::time(1000); raw->state.subscribedTill = ms ? (crl::now() + ms) : -1; - _session->data().processUsers(data.vusers()); - + const auto was = myStateKey(raw->state); apply(raw, data.vstate()); apply(raw, data.vuser_state()); if (raw->changes.has_consumers()) { @@ -162,6 +312,9 @@ void GiftAuctions::request(const QString &slug) { _timer.callOnce(ms); } } + if (was != myStateKey(raw->state)) { + _activeChanged.fire({}); + } }).send(); } @@ -177,49 +330,54 @@ GiftAuctions::Entry *GiftAuctions::find(uint64 giftId) const { void GiftAuctions::apply( not_null entry, const MTPStarGiftAuctionState &state) { - Expects(entry->state.gift.has_value()); + apply(&entry->state, state); +} + +void GiftAuctions::apply( + not_null entry, + const MTPStarGiftAuctionState &state) { + Expects(entry->gift.has_value()); - const auto raw = &entry->state; state.match([&](const MTPDstarGiftAuctionState &data) { const auto version = data.vversion().v; - if (raw->version >= version) { + if (entry->version >= version) { return; } const auto owner = &_session->data(); - raw->startDate = data.vstart_date().v; - raw->endDate = data.vend_date().v; - raw->minBidAmount = data.vmin_bid_amount().v; + entry->startDate = data.vstart_date().v; + entry->endDate = data.vend_date().v; + entry->minBidAmount = data.vmin_bid_amount().v; const auto &levels = data.vbid_levels().v; - raw->bidLevels.clear(); - raw->bidLevels.reserve(levels.size()); + entry->bidLevels.clear(); + entry->bidLevels.reserve(levels.size()); for (const auto &level : levels) { - auto &entry = raw->bidLevels.emplace_back(); + auto &bid = entry->bidLevels.emplace_back(); const auto &data = level.data(); - entry.amount = data.vamount().v; - entry.position = data.vpos().v; - entry.date = data.vdate().v; + bid.amount = data.vamount().v; + bid.position = data.vpos().v; + bid.date = data.vdate().v; } const auto &top = data.vtop_bidders().v; - raw->topBidders.clear(); - raw->topBidders.reserve(top.size()); + entry->topBidders.clear(); + entry->topBidders.reserve(top.size()); for (const auto &user : top) { - raw->topBidders.push_back(owner->user(UserId(user.v))); + entry->topBidders.push_back(owner->user(UserId(user.v))); } - raw->nextRoundAt = data.vnext_round_at().v; - raw->giftsLeft = data.vgifts_left().v; - raw->currentRound = data.vcurrent_round().v; - raw->totalRounds = data.vtotal_rounds().v; - raw->averagePrice = 0; + entry->nextRoundAt = data.vnext_round_at().v; + entry->giftsLeft = data.vgifts_left().v; + entry->currentRound = data.vcurrent_round().v; + entry->totalRounds = data.vtotal_rounds().v; + entry->averagePrice = 0; }, [&](const MTPDstarGiftAuctionStateFinished &data) { - raw->averagePrice = data.vaverage_price().v; - raw->startDate = data.vstart_date().v; - raw->endDate = data.vend_date().v; - raw->minBidAmount = 0; - raw->nextRoundAt - = raw->currentRound - = raw->totalRounds - = raw->giftsLeft - = raw->version + entry->averagePrice = data.vaverage_price().v; + entry->startDate = data.vstart_date().v; + entry->endDate = data.vend_date().v; + entry->minBidAmount = 0; + entry->nextRoundAt + = entry->currentRound + = entry->totalRounds + = entry->giftsLeft + = entry->version = 0; }, [&](const MTPDstarGiftAuctionStateNotModified &data) { }); @@ -228,16 +386,32 @@ void GiftAuctions::apply( void GiftAuctions::apply( not_null entry, const MTPStarGiftAuctionUserState &state) { + apply(&entry->state.my, state); +} + +void GiftAuctions::apply( + not_null entry, + const MTPStarGiftAuctionUserState &state) { const auto &data = state.data(); - const auto raw = &entry->state.my; - raw->to = data.vbid_peer() + entry->to = data.vbid_peer() ? _session->data().peer(peerFromMTP(*data.vbid_peer())).get() : nullptr; - raw->minBidAmount = data.vmin_bid_amount().value_or(0); - raw->bid = data.vbid_amount().value_or(0); - raw->date = data.vbid_date().value_or(0); - raw->gotCount = data.vacquired_count().v; - raw->returned = data.is_returned(); + entry->minBidAmount = data.vmin_bid_amount().value_or(0); + entry->bid = data.vbid_amount().value_or(0); + entry->date = data.vbid_date().value_or(0); + entry->gotCount = data.vacquired_count().v; + entry->returned = data.is_returned(); +} + +int MyAuctionPosition(const GiftAuctionState &state) { + const auto &levels = state.bidLevels; + for (auto i = begin(levels), e = end(levels); i != e; ++i) { + if (i->amount < state.my.bid + || (i->amount == state.my.bid && i->date > state.my.date)) { + return i->position; + } + } + return (levels.empty() ? 0 : levels.back().position) + 1; } } // namespace Data diff --git a/Telegram/SourceFiles/data/components/gift_auctions.h b/Telegram/SourceFiles/data/components/gift_auctions.h index d86a342c32..ba8012707d 100644 --- a/Telegram/SourceFiles/data/components/gift_auctions.h +++ b/Telegram/SourceFiles/data/components/gift_auctions.h @@ -62,6 +62,10 @@ struct GiftAcquired { bool nameHidden = false; }; +struct ActiveAuctions { + std::vector> list; +}; + class GiftAuctions final { public: explicit GiftAuctions(not_null session); @@ -73,31 +77,63 @@ public: void apply(const MTPDupdateStarGiftAuctionUserState &data); void requestAcquired( - uint64 giftId, + uint64 giftId, Fn)> done); + [[nodiscard]] rpl::producer active() const; + [[nodiscard]] rpl::producer hasActiveChanges() const; + [[nodiscard]] bool hasActive() const; + private: struct Entry { GiftAuctionState state; rpl::event_stream<> changes; bool requested = false; }; + struct MyStateKey { + int bid = 0; + int position = 0; + int version = 0; + + explicit operator bool() const { + return bid != 0; + } + friend inline bool operator==(MyStateKey, MyStateKey) = default; + }; void request(const QString &slug); Entry *find(uint64 giftId) const; void apply( not_null entry, const MTPStarGiftAuctionState &state); + void apply( + not_null entry, + const MTPStarGiftAuctionState &state); void apply( not_null entry, const MTPStarGiftAuctionUserState &state); + void apply( + not_null entry, + const MTPStarGiftAuctionUserState &state); void checkSubscriptions(); + [[nodiscard]] MyStateKey myStateKey(const GiftAuctionState &state) const; + [[nodiscard]] ActiveAuctions collectActive() const; + [[nodiscard]] uint64 countActiveHash() const; + void requestActive(); + const not_null _session; base::Timer _timer; base::flat_map> _map; + rpl::event_stream<> _activeChanged; + mtpRequestId _activeRequestId = 0; + + rpl::lifetime _lifetime; + }; +[[nodiscard]] int MyAuctionPosition(const GiftAuctionState &state); + } // namespace Data diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index fd9a620c3c..a85d68fc2e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -129,6 +129,11 @@ dialogRowOpenBot: DialogRightButton { dialogRowOpenBotRecent: DialogRightButton(dialogRowOpenBot) { margin: margins(0px, 32px, 16px, 0px); } +dialogsTopBarRightButton: RoundButton(defaultActiveButton) { + width: -16px; + height: 22px; + textTop: 2px; +} forumDialogJumpArrow: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFg }}; forumDialogJumpArrowOver: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFgOver }}; diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp index 8d25e709f7..91c4a177b3 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp @@ -14,9 +14,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/call_delayed.h" #include "boxes/star_gift_box.h" // ShowStarGiftBox. +#include "boxes/star_gift_auction_box.h" #include "core/application.h" #include "core/click_handler_types.h" #include "core/ui_integration.h" +#include "data/components/gift_auctions.h" #include "data/components/promo_suggestions.h" #include "data/data_birthday.h" #include "data/data_changes.h" @@ -184,6 +186,7 @@ rpl::producer*> TopBarSuggestionValue( rpl::lifetime userpicLifetime; rpl::lifetime giftsLifetime; rpl::lifetime creditsLifetime; + rpl::lifetime auctionsLifetime; std::unique_ptr creditsHistory; }; @@ -229,6 +232,7 @@ rpl::producer*> TopBarSuggestionValue( state->userpicLifetime.destroy(); state->giftsLifetime.destroy(); state->creditsLifetime.destroy(); + state->auctionsLifetime.destroy(); if (!session->api().authorizations().unreviewed().empty()) { state->content = nullptr; @@ -273,7 +277,51 @@ rpl::producer*> TopBarSuggestionValue( const auto wrap = state->wrap.get(); using RightIcon = TopBarSuggestionContent::RightIcon; const auto promo = &session->promoSuggestions(); - if (const auto custom = promo->custom()) { + const auto auctions = &session->giftAuctions(); + if (auctions->hasActive()) { + using namespace Data; + struct Button { + rpl::variable text; + Fn callback; + base::has_weak_ptr guard; + }; + const auto button = state->auctionsLifetime.make_state< + Button + >(); + const auto window = FindSessionController(parent); + auctions->active( + ) | rpl::start_with_next([=](ActiveAuctions &&active) { + const auto empty = active.list.empty(); + state->desiredWrapToggle.force_assign( + Toggle{ !empty, anim::type::normal }); + if (empty) { + return; + } + + auto text = Ui::ActiveAuctionsState(active); + const auto textColorOverride = text.someOutbid + ? st::attentionButtonFg->c + : std::optional(); + content->setContent( + Ui::ActiveAuctionsTitle(active), + std::move(text.text), + Core::TextContext({ .session = session }), + textColorOverride); + button->text = Ui::ActiveAuctionsButton(active); + button->callback = Ui::ActiveAuctionsCallback( + window, + active); + }, state->auctionsLifetime); + const auto callback = crl::guard(&button->guard, [=] { + button->callback(); + }); + content->setRightButton(button->text.value(), callback); + content->setClickedCallback(callback); + content->setLeftPadding(state->leftPadding.value()); + state->desiredWrapToggle.force_assign( + Toggle{ true, anim::type::normal }); + return; + } else if (const auto custom = promo->custom()) { content->setRightIcon(RightIcon::Close); content->setLeftPadding(state->leftPadding.value()); content->setClickedCallback([=] { @@ -733,12 +781,14 @@ rpl::producer*> TopBarSuggestionValue( rpl::merge( session->promoSuggestions().value(), session->api().authorizations().unreviewedChanges(), - Data::AmPremiumValue(session) | rpl::skip(1) | rpl::to_empty + Data::AmPremiumValue(session) | rpl::skip(1) | rpl::to_empty, + session->giftAuctions().hasActiveChanges() | rpl::to_empty ) | rpl::start_with_next([=] { const auto was = state->wrap.get(); + const auto weak = base::make_weak(was); processCurrentSuggestion(processCurrentSuggestion); - if (was != state->wrap) { - consumer.put_next_copy(state->wrap); + if (was != state->wrap || (was && !weak)) { + consumer.put_next_copy(state->wrap.get()); } }, lifetime); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp index 04acfa5b88..e1e7386205 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp @@ -165,6 +165,7 @@ TopBarSuggestionContent::TopBarSuggestionContent(not_null p) } void TopBarSuggestionContent::setRightIcon(RightIcon icon) { + _rightButton = nullptr; if (icon == _rightIcon) { return; } @@ -201,6 +202,35 @@ void TopBarSuggestionContent::setRightIcon(RightIcon icon) { } } +void TopBarSuggestionContent::setRightButton( + rpl::producer text, + Fn callback) { + _rightHide = nullptr; + _rightArrow = nullptr; + _rightIcon = RightIcon::None; + if (!text) { + _rightButton = nullptr; + return; + } + using namespace Ui; + _rightButton = base::make_unique_q( + this, + rpl::single(QString()), + st::dialogsTopBarRightButton); + _rightButton->setText(std::move(text)); + rpl::combine( + sizeValue(), + _rightButton->sizeValue() + ) | rpl::start_with_next([=](QSize outer, QSize inner) { + const auto top = (outer.height() - inner.height()) / 2; + _rightButton->moveToRight(top, top, outer.width()); + }, _rightButton->lifetime()); + _rightButton->setFullRadius(true); + _rightButton->setTextTransform(RoundButton::TextTransform::NoTransform); + _rightButton->setClickedCallback(std::move(callback)); + _rightButton->show(); +} + void TopBarSuggestionContent::draw(QPainter &p) { const auto kLinesForPhoto = 3; @@ -270,7 +300,7 @@ void TopBarSuggestionContent::draw(QPainter &p) { : availableWidth, }; }; - p.setPen(st::windowSubTextFg); + p.setPen(_descriptionColorOverride.value_or(st::windowSubTextFg->c)); _contentText.draw(p, { .position = QPoint(left, top), .outerWidth = availableWidth, @@ -288,7 +318,9 @@ void TopBarSuggestionContent::draw(QPainter &p) { void TopBarSuggestionContent::setContent( TextWithEntities title, TextWithEntities description, - std::optional context) { + std::optional context, + std::optional descriptionColorOverride) { + _descriptionColorOverride = descriptionColorOverride; if (context) { context->repaint = [=] { update(); }; _contentTitle.setMarkedText( @@ -305,6 +337,7 @@ void TopBarSuggestionContent::setContent( _contentTitle.setMarkedText(_contentTitleSt, std::move(title)); _contentText.setMarkedText(_contentTextSt, std::move(description)); } + update(); } void TopBarSuggestionContent::paintEvent(QPaintEvent *) { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h index 432f29fcc2..9a3a2efb8e 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h @@ -45,12 +45,16 @@ public: void setContent( TextWithEntities title, TextWithEntities description, - std::optional context = std::nullopt); + std::optional context = std::nullopt, + std::optional descriptionColorOverride = std::nullopt); [[nodiscard]] rpl::producer desiredHeightValue() const override; void setHideCallback(Fn); void setRightIcon(RightIcon); + void setRightButton( + rpl::producer text, + Fn callback); void setLeftPadding(rpl::producer); [[nodiscard]] const style::TextStyle &contentTitleSt() const; @@ -69,9 +73,11 @@ private: Ui::Text::String _contentText; rpl::variable _lastPaintedContentLineAmount = 0; rpl::variable _lastPaintedContentTop = 0; + std::optional _descriptionColorOverride; base::unique_qptr _rightHide; base::unique_qptr _rightArrow; + base::unique_qptr _rightButton; Fn _hideCallback; int _leftPadding = 0; diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 5cf630d3b1..adb5ffed15 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -444,6 +444,15 @@ videoStreamStarsCover: PremiumCover(creditsLowBalancePremiumCover) { auctionInfoPreviewMargin: margins(0px, 24px, 0px, 8px); auctionInfoSubtitleSkip: 8px; auctionInfoTableMargin: margins(0px, 12px, 0px, 12px); +auctionBidEmoji: IconEmoji { + icon: icon {{ "settings/button_auction", windowFg }}; + padding: margins(-4px, -1px, -4px, 0px); +} +auctionBidToast: Toast(defaultToast) { + padding: margins(54px, 13px, 19px, 12px); + icon: icon {{ "settings/toast_auction", toastFg }}; + iconPosition: point(18px, 18px); +} auctionAboutLogo: icon {{ "settings/large_auctions", windowFgActive }}; auctionAboutLogoPadding: margins(8px, 8px, 8px, 8px); auctionCenteredSubtitle: FlatLabel(defaultFlatLabel) { @@ -470,3 +479,19 @@ auctionBidStars: FlatLabel(defaultFlatLabel) { textFg: windowSubTextFg; } auctionBidSkip: 10px; + +auctionListEntrySkip: 12px; +auctionListTitle: FlatLabel(defaultFlatLabel) { + style: TextStyle(defaultTextStyle) { + font: font(15px semibold); + } + textFg: windowBoldFg; +} +auctionListTitlePadding: margins(50px, 0px, 0px, 0px); +auctionListText: FlatLabel(defaultFlatLabel) { +} +auctionListTextPadding: margins(50px, 4px, 0px, 0px); +auctionListRaise: RoundButton(defaultActiveButton) { + width: 0px; +} +auctionListRaisePadding: margins(0px, 8px, 0px, 0px); diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 9fbbcfd466..be555efdb2 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 9fbbcfd46679f9f61ff072beb8bf507166f852a2 +Subproject commit be555efdb20de610f55fde6dc8f670e2114e26fd