diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 3805755b71..ed41b8e676 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -333,6 +333,8 @@ PRIVATE boxes/send_files_box.h boxes/share_box.cpp boxes/share_box.h + boxes/star_gift_auction_box.cpp + boxes/star_gift_auction_box.h boxes/star_gift_box.cpp boxes/star_gift_box.h boxes/star_gift_resale_box.cpp @@ -1644,6 +1646,8 @@ PRIVATE ui/controls/location_picker.h ui/controls/silent_toggle.cpp ui/controls/silent_toggle.h + ui/controls/table_rows.cpp + ui/controls/table_rows.h ui/controls/userpic_button.cpp ui/controls/userpic_button.h ui/effects/credits_graphics.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a39fcb3de6..3382ae733c 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4003,8 +4003,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_auction_about_missed_title" = "Missed Bidders"; "lng_auction_about_missed_about" = "If your bid doesn't win after the final round, your Stars will be fully refunded."; "lng_auction_about_understood" = "Understood"; -"lng_auction_text#one" = "Top **{count}** bidder will get an **{name}** this round. {link}"; -"lng_auction_text#other" = "Top **{count}** bidders will get an **{name}** this round. {link}"; +"lng_auction_text#one" = "Top **{count}** bidder will get 1 {name} this round. {link}"; +"lng_auction_text#other" = "Top **{count}** bidders will get 1 {name} this round. {link}"; "lng_auction_text_link" = "Learn more {arrow}"; "lng_auction_text_ended" = "Auction ended."; "lng_auction_start_label" = "Started"; @@ -4012,13 +4012,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_auction_round_label" = "Current Round"; "lng_auction_round_value" = "{n} of {amount}"; "lng_auction_average_label" = "Average Price"; +"lng_auction_average_tooltip" = "{amount} is the average sale price for {gift} gifts."; "lng_auction_availability_label" = "Availability"; "lng_auction_availability_value" = "{n} of {amount} left"; "lng_auction_bought#one" = "{count} {emoji} item bought {arrow}"; "lng_auction_bought#other" = "{count} {emoji} items bought {arrow}"; "lng_auction_join_button" = "Join Auction"; "lng_auction_join_time_left" = "{time} left"; -"lng_auction_join_time_big" = "{hours} h {minutes} m"; +"lng_auction_join_time_medium" = "{hours} h {minutes} m"; "lng_auction_join_time_small" = "{minutes} m"; "lng_auction_menu_about" = "About"; "lng_auction_menu_copy_link" = "Copy Link"; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index d11d006181..d2cc09afa1 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -856,9 +856,10 @@ std::optional FromTL( .starsResellMin = int64(resellPrice), .document = document, .releasedBy = releasedBy, - .auctionSlug = qs(data.vauction_slug().value_or_empty()), .resellTitle = qs(data.vtitle().value_or_empty()), .resellCount = int(data.vavailability_resale().value_or_empty()), + .auctionSlug = qs(data.vauction_slug().value_or_empty()), + .auctionGiftsPerRound = data.vgifts_per_round().value_or_empty(), .limitedLeft = remaining.value_or_empty(), .limitedCount = total.value_or_empty(), .perUserTotal = data.vper_user_total().value_or_empty(), @@ -870,7 +871,6 @@ std::optional FromTL( .peerColorAvailable = data.is_peer_color_available(), .upgradable = data.vupgrade_stars().has_value(), .birthday = data.is_birthday(), - .auction = data.is_auction(), .soldOut = data.is_sold_out(), }); }, [&](const MTPDstarGiftUnique &data) { diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 18a876538c..4394d71414 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/mtproto_dc_options.h" #include "data/business/data_shortcut_messages.h" #include "data/components/credits.h" +#include "data/components/gift_auctions.h" #include "data/components/promo_suggestions.h" #include "data/components/scheduled_messages.h" #include "data/components/top_peers.h" @@ -2789,6 +2790,15 @@ void Updates::feedUpdate(const MTPUpdate &update) { Api::ParsePaidReactionShownPeer(_session, data.vprivate())); } break; + case mtpc_updateStarGiftAuctionState: { + const auto &data = update.c_updateStarGiftAuctionState(); + _session->giftAuctions().apply(data); + } break; + + case mtpc_updateStarGiftAuctionUserState: { + const auto &data = update.c_updateStarGiftAuctionUserState(); + _session->giftAuctions().apply(data); + } break; } } diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 757289e9a5..60ee15bc81 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -31,10 +31,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_premium_subscription_option.h" #include "data/data_user.h" #include "data/stickers/data_custom_emoji.h" -#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget. +//#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget. #include "info/channel_statistics/earn/earn_icons.h" -#include "info/profile/info_profile_badge.h" -#include "info/profile/info_profile_values.h" +//#include "info/profile/info_profile_badge.h" +//#include "info/profile/info_profile_values.h" #include "lang/lang_keys.h" #include "main/main_app_config.h" #include "main/main_session.h" @@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/boost_box.h" // StartFireworks. #include "ui/boxes/confirm_box.h" #include "ui/controls/userpic_button.h" +#include "ui/controls/table_rows.h" #include "ui/effects/credits_graphics.h" #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_stars_colored.h" @@ -81,79 +82,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { constexpr auto kTooltipDuration = 3 * crl::time(1000); -constexpr auto kPriceTooltipDuration = 6 * crl::time(1000); constexpr auto kHorizontalBar = QChar(0x2015); -struct InfoTooltipData { - not_null parent; - Ui::ImportantTooltip *raw = nullptr; -}; - -void ShowInfoTooltip( - std::shared_ptr data, - not_null target, - rpl::producer text, - int duration) { - if (data->raw) { - data->raw->toggleAnimated(false); - } - const auto parent = data->parent; - const auto tooltip = Ui::CreateChild( - parent, - Ui::MakeNiceTooltipLabel( - parent, - std::move(text), - st::boxWideWidth, - st::defaultImportantTooltipLabel), - st::defaultImportantTooltip); - tooltip->toggleFast(false); - - base::install_event_filter(tooltip, qApp, [=](not_null e) { - if (e->type() == QEvent::MouseButtonPress) { - tooltip->toggleAnimated(false); - } - return base::EventFilterResult::Continue; - }); - - const auto update = [=] { - const auto geometry = Ui::MapFrom(parent, target, target->rect()); - const auto countPosition = [=](QSize size) { - const auto left = geometry.x() - + (geometry.width() - size.width()) / 2; - const auto right = parent->width() - - st::normalFont->spacew; - return QPoint( - std::max(std::min(left, right - size.width()), 0), - geometry.y() - size.height() - st::normalFont->descent); - }; - tooltip->pointAt(geometry, RectPart::Top, countPosition); - }; - parent->widthValue( - ) | rpl::start_with_next(update, tooltip->lifetime()); - - update(); - tooltip->toggleAnimated(true); - - data->raw = tooltip; - tooltip->shownValue() | rpl::filter( - !rpl::mappers::_1 - ) | rpl::start_with_next([=] { - crl::on_main(tooltip, [=] { - if (tooltip->isHidden()) { - if (data->raw == tooltip) { - data->raw = nullptr; - } - delete tooltip; - } - }); - }, tooltip->lifetime()); - - base::timer_once( - duration - ) | rpl::start_with_next([=] { - tooltip->toggleAnimated(false); - }, tooltip->lifetime()); -} +using Ui::AddTableRow; +using Ui::TableRowTooltipData; [[nodiscard]] QString CreateMessageLink( not_null session, @@ -262,230 +194,6 @@ void ShowInfoTooltip( : st::giveawayGiftCodeValueMultiline)); } -[[nodiscard]] object_ptr MakeValueWithSmallButton( - not_null table, - not_null value, - rpl::producer buttonText, - Fn button)> handler = nullptr, - int topSkip = 0) { - class MarginedWidget final : public Ui::RpWidget { - public: - using RpWidget::RpWidget; - QMargins getMargins() const override { - return { 0, 0, 0, st::giveawayGiftCodePeerMargin.bottom() }; - } - }; - auto result = object_ptr(table); - const auto raw = result.data(); - - value->setParent(raw); - value->show(); - - const auto button = Ui::CreateChild( - raw, - std::move(buttonText), - table->st().smallButton); - button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); - if (handler) { - button->setClickedCallback([button, handler = std::move(handler)] { - handler(button); - }); - } else { - button->setAttribute(Qt::WA_TransparentForMouseEvents); - } - rpl::combine( - raw->widthValue(), - button->widthValue(), - value->naturalWidthValue() - ) | rpl::start_with_next([=](int width, int buttonWidth, int) { - const auto buttonSkip = st::normalFont->spacew + buttonWidth; - value->resizeToNaturalWidth(width - buttonSkip); - value->moveToLeft(0, 0, width); - button->moveToLeft( - rect::right(value) + st::normalFont->spacew, - (topSkip - + (table->st().defaultValue.style.font->ascent - - table->st().smallButton.style.font->ascent)), - width); - }, value->lifetime()); - - value->heightValue() | rpl::start_with_next([=](int height) { - const auto bottom = st::giveawayGiftCodePeerMargin.bottom(); - raw->resize(raw->width(), height + bottom); - }, raw->lifetime()); - - return result; -} - -[[nodiscard]] object_ptr MakePeerTableValue( - not_null table, - std::shared_ptr show, - PeerId id, - rpl::producer button = nullptr, - Fn handler = nullptr) { - auto result = object_ptr(table); - const auto raw = result.data(); - - const auto &st = st::giveawayGiftCodeUserpic; - raw->resize(raw->width(), st.photoSize); - - const auto peer = show->session().data().peer(id); - const auto userpic = Ui::CreateChild(raw, peer, st); - const auto label = Ui::CreateChild( - raw, - (button && handler) ? peer->shortName() : peer->name(), - table->st().defaultValue); - - raw->widthValue() | rpl::start_with_next([=](int width) { - const auto position = st::giveawayGiftCodeNamePosition; - label->resizeToNaturalWidth(width - position.x()); - label->moveToLeft(position.x(), position.y(), width); - const auto top = (raw->height() - userpic->height()) / 2; - userpic->moveToLeft(0, top, width); - }, label->lifetime()); - - label->naturalWidthValue() | rpl::start_with_next([=](int width) { - raw->setNaturalWidth(st::giveawayGiftCodeNamePosition.x() + width); - }, label->lifetime()); - userpic->setAttribute(Qt::WA_TransparentForMouseEvents); - label->setAttribute(Qt::WA_TransparentForMouseEvents); - label->setTextColorOverride(table->st().defaultValue.palette.linkFg->c); - - raw->setClickedCallback([=] { - show->showBox(PrepareShortInfoBox(peer, show)); - }); - - if (!button || !handler) { - return result; - } - return MakeValueWithSmallButton( - table, - result.release(), - std::move(button), - [=](not_null button) { handler(); }, - st::giveawayGiftCodeNamePosition.y()); -} - -[[nodiscard]] object_ptr MakePeerWithStatusValue( - not_null table, - std::shared_ptr show, - PeerId id, - Fn, EmojiStatusId)> pushStatusId) { - auto result = object_ptr(table); - const auto raw = result.data(); - - const auto peerLabel = MakePeerTableValue(table, show, id).release(); - peerLabel->setParent(raw); - peerLabel->show(); - - raw->resize(raw->width(), peerLabel->height()); - - using namespace Info::Profile; - struct State { - rpl::variable content; - }; - const auto peer = show->session().data().peer(id); - const auto state = peerLabel->lifetime().make_state(); - state->content = EmojiStatusIdValue( - peer - ) | rpl::map([=](EmojiStatusId emojiStatusId) { - if (!peer->session().premium() - || (!peer->isSelf() && !emojiStatusId)) { - return Badge::Content(); - } - return Badge::Content{ - .badge = BadgeType::Premium, - .emojiStatusId = emojiStatusId, - }; - }); - const auto badge = peerLabel->lifetime().make_state( - raw, - st::infoPeerBadge, - &peer->session(), - state->content.value(), - nullptr, - [=] { return show->paused(ChatHelpers::PauseReason::Layer); }); - state->content.value( - ) | rpl::start_with_next([=](const Badge::Content &content) { - if (const auto widget = badge->widget()) { - pushStatusId(widget, content.emojiStatusId); - } - }, raw->lifetime()); - - rpl::combine( - raw->widthValue(), - rpl::single(rpl::empty) | rpl::then(badge->updated()) - ) | rpl::start_with_next([=](int width, const auto &) { - const auto badgeWidget = badge->widget(); - const auto badgeSkip = badgeWidget - ? (st::normalFont->spacew + badgeWidget->width()) - : 0; - peerLabel->resizeToNaturalWidth(width - badgeSkip); - peerLabel->moveToLeft(0, 0, width); - if (badgeWidget) { - badgeWidget->moveToLeft( - peerLabel->width() + st::normalFont->spacew, - st::giftBoxByStarsStarTop, - width); - } - }, raw->lifetime()); - - return result; -} - -[[nodiscard]] object_ptr MakeHiddenPeerTableValue( - not_null table) { - auto result = object_ptr(table); - const auto raw = result.data(); - - const auto &st = st::giveawayGiftCodeUserpic; - raw->resize(raw->width(), st.photoSize); - - const auto userpic = Ui::CreateChild(raw); - const auto usize = st.photoSize; - userpic->resize(usize, usize); - userpic->paintRequest() | rpl::start_with_next([=] { - auto p = QPainter(userpic); - Ui::EmptyUserpic::PaintHiddenAuthor(p, 0, 0, usize, usize); - }, userpic->lifetime()); - - const auto label = Ui::CreateChild( - raw, - tr::lng_gift_from_hidden(), - table->st().defaultValue); - raw->widthValue( - ) | rpl::start_with_next([=](int width) { - const auto position = st::giveawayGiftCodeNamePosition; - label->resizeToNaturalWidth(width - position.x()); - label->moveToLeft(position.x(), position.y(), width); - const auto top = (raw->height() - userpic->height()) / 2; - userpic->moveToLeft(0, top, width); - }, label->lifetime()); - - userpic->setAttribute(Qt::WA_TransparentForMouseEvents); - label->setAttribute(Qt::WA_TransparentForMouseEvents); - label->setTextColorOverride(st::windowFg->c); - - return result; -} - -void AddTableRow( - not_null table, - rpl::producer label, - object_ptr value, - style::margins valueMargins = st::giveawayGiftCodeValueMargin) { - table->addRow( - (label - ? object_ptr( - table, - std::move(label), - table->st().defaultLabel) - : object_ptr(nullptr)), - std::move(value), - st::giveawayGiftCodeLabelMargin, - valueMargins); -} - [[nodiscard]] object_ptr MakePriceWithChangePercentValue( not_null table, const std::shared_ptr &value) { @@ -511,35 +219,13 @@ void AddTableRow( return MakeValueWithSmallButton(table, label, std::move(text)); } -[[nodiscard]] object_ptr MakePriceValueWithTooltip( - not_null table, - std::shared_ptr data, - TextWithEntities price, - TextWithEntities tooltip) { - const auto label = Ui::CreateChild( - table, - rpl::single(price), - table->st().defaultValue); - label->setAttribute(Qt::WA_TransparentForMouseEvents); - - const auto handler = [=](not_null button) { - ShowInfoTooltip( - data, - button, - rpl::single(tooltip), - kPriceTooltipDuration); - }; - auto text = rpl::single(u"?"_q); - return MakeValueWithSmallButton(table, label, std::move(text), handler); -} - [[nodiscard]] object_ptr MakeMinimumPriceValue( not_null table, - std::shared_ptr tooltip, + std::shared_ptr tooltip, const std::shared_ptr &unique) { const auto &value = unique->value; const auto text = FormatValuePrice(value->minimumPrice, value->currency); - return MakePriceValueWithTooltip( + return Ui::MakeTableValueWithTooltip( table, std::move(tooltip), text, @@ -554,11 +240,11 @@ void AddTableRow( [[nodiscard]] object_ptr MakeAveragePriceValue( not_null table, - std::shared_ptr tooltip, + std::shared_ptr tooltip, const std::shared_ptr &unique) { const auto &value = unique->value; const auto text = FormatValuePrice(value->averagePrice, value->currency); - return MakePriceValueWithTooltip( + return Ui::MakeTableValueWithTooltip( table, std::move(tooltip), text, @@ -594,13 +280,12 @@ void AddUniqueGiftPropertyRows( not_null container, not_null table, not_null unique) { - const auto tooltip = std::make_shared(InfoTooltipData{ - .parent = container, - }); + const auto tooltip = std::make_shared( + TableRowTooltipData{ .parent = container }); const auto showTooltip = [=]( not_null widget, rpl::producer text) { - ShowInfoTooltip(tooltip, widget, std::move(text), kTooltipDuration); + ShowTableRowTooltip(tooltip, widget, std::move(text), kTooltipDuration); }; const auto showRarity = [=]( not_null widget, @@ -714,37 +399,6 @@ void AddUniqueGiftPropertyRows( handler); } -not_null AddTableRow( - not_null table, - rpl::producer label, - rpl::producer value, - const Ui::Text::MarkedContext &context = {}) { - auto widget = object_ptr( - table, - std::move(value), - table->st().defaultValue, - st::defaultPopupMenu, - context); - const auto result = widget.data(); - AddTableRow(table, std::move(label), std::move(widget)); - return result; -} - -void AddTableRow( - not_null table, - rpl::producer label, - std::shared_ptr show, - PeerId id) { - if (!id) { - return; - } - AddTableRow( - table, - std::move(label), - MakePeerTableValue(table, show, id), - st::giveawayGiftCodePeerMargin); -} - void AddTable( not_null container, std::shared_ptr show, @@ -1557,13 +1211,12 @@ void AddStarGiftTable( const auto giftToChannel = entry.giftChannelSavedId && peerIsChannel(PeerId(entry.bareEntryOwnerId)); - const auto tooltip = std::make_shared(InfoTooltipData{ - .parent = container, - }); + const auto tooltip = std::make_shared( + TableRowTooltipData{ .parent = container }); const auto showTooltip = [=]( not_null widget, rpl::producer text) { - ShowInfoTooltip(tooltip, widget, std::move(text), kTooltipDuration); + ShowTableRowTooltip(tooltip, widget, std::move(text), kTooltipDuration); }; if (unique && entry.bareGiftResaleRecipientId) { @@ -2320,9 +1973,8 @@ void AddUniqueGiftValueTable( MakePriceWithChangePercentValue(table, value)); } - const auto tooltip = std::make_shared(InfoTooltipData{ - .parent = container, - }); + const auto tooltip = std::make_shared( + TableRowTooltipData{ .parent = container }); if (value->minimumPrice) { AddTableRow( table, diff --git a/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp b/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp new file mode 100644 index 0000000000..7b761f4e21 --- /dev/null +++ b/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp @@ -0,0 +1,521 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "boxes/star_gift_auction_box.h" + +#include "base/unixtime.h" +#include "boxes/star_gift_box.h" +#include "core/credits_amount.h" +#include "data/components/credits.h" +#include "data/components/gift_auctions.h" +#include "data/data_message_reactions.h" +#include "data/data_user.h" +#include "info/channel_statistics/earn/earn_icons.h" +#include "info/peer_gifts/info_peer_gifts_common.h" +#include "lang/lang_keys.h" +#include "main/main_app_config.h" +#include "main/main_session.h" +#include "payments/ui/payments_reaction_box.h" +#include "payments/payments_checkout_process.h" +#include "ui/controls/button_labels.h" +#include "ui/controls/table_rows.h" +#include "ui/layers/generic_box.h" +#include "ui/text/format_values.h" +#include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" +#include "ui/widgets/buttons.h" +#include "ui/wrap/table_layout.h" +#include "ui/dynamic_thumbnails.h" +#include "window/window_session_controller.h" +#include "styles/style_chat.h" +#include "styles/style_layers.h" +#include "styles/style_credits.h" + +namespace Ui { +namespace { + +[[nodiscard]] rpl::producer MinutesLeftTillValue(TimeId endDate) { + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + + const auto now = base::unixtime::now(); + if (endDate <= now) { + consumer.put_next(0); + consumer.put_done(); + return lifetime; + } + + const auto timer = lifetime.make_state(); + const auto callback = [=] { + const auto now = base::unixtime::now(); + const auto left = (endDate > now) ? endDate - now : 0; + const auto minutes = (left + 59) / 60; + consumer.put_next_copy(minutes); + if (minutes) { + const auto next = left % 60; + const auto wait = next ? next : 60; + timer->callOnce(wait * crl::time(1000)); + } else { + consumer.put_done(); + } + }; + timer->setCallback(callback); + callback(); + + return lifetime; + }; +} + +struct AuctionBidBoxArgs { + std::shared_ptr show; + rpl::producer state; + Fn save; +}; + +void PlaceAuctionBid( + std::shared_ptr show, + not_null to, + int64 amount, + const Data::GiftAuctionState &state, + Fn done) { + auto paymentDone = [=]( + Payments::CheckoutResult result, + const MTPUpdates *updates) { + done(result); + }; + const auto invoice = state.my.bid + ? MTP_inputInvoiceStarGiftAuctionUpdateBid( + MTP_long(state.gift->id), + MTP_long(amount)) + : MTP_inputInvoiceStarGiftAuctionBid( + MTP_flags(0), + to->input, + MTP_long(state.gift->id), + MTP_long(amount), + MTPTextWithEntities()); + RequestOurForm(show, invoice, [=]( + uint64 formId, + CreditsAmount price, + std::optional failure) { + if (failure) { + paymentDone(*failure, nullptr); + } else { + SubmitStarsForm( + show, + invoice, + formId, + price.whole(), + paymentDone); + } + }); +} + +void AuctionBidBox(not_null box, AuctionBidBoxArgs &&args) { + const auto weak = base::make_weak(box); + const auto state = box->lifetime().make_state< + rpl::variable + >(std::move(args.state)); + auto submit = [=](rpl::producer amount) { + return std::move(amount) | rpl::map([=](int count) { + return TextWithEntities{ "Place a " + QString::number(count) + " Bid" }; + }); + }; + const auto show = args.show; + const auto session = &show->session(); + auto top = std::vector{ + PaidReactionTop{ + .name = session->user()->shortName(), + .photo = MakeUserpicThumbnail(session->user()), + .barePeerId = uint64(session->userPeerId().value), + .count = int(state->current().my.bid), + .my = true, + } + }; + PaidReactionsBox(box, { + .min = int(state->current().my.bid + ? state->current().my.minBidAmount + : state->current().minBidAmount), + .chosen = int(state->current().my.bid), + .max = 10'000, + .top = std::move(top), + .session = session, + .name = u"hello"_q, + .submit = submit, + .colorings = session->appConfig().groupCallColorings(), + .balanceValue = session->credits().balanceValue(), + .send = [weak, save = args.save](int count, uint64 barePeerId) { + save(count); + if (const auto strong = weak.get()) { + strong->closeBox(); + } + }, + .giftAuction = true, + }); +} + +[[nodiscard]] object_ptr MakeAuctionBidBox( + AuctionBidBoxArgs &&args) { + return Box(AuctionBidBox, std::move(args)); +} + +[[nodiscard]] object_ptr MakeAveragePriceValue( + not_null table, + std::shared_ptr tooltip, + const QString &name, + int64 amount) { + auto helper = Ui::Text::CustomEmojiHelper(); + const auto price = helper.paletteDependent(Ui::Earn::IconCreditsEmoji( + )).append(' ').append( + Lang::FormatCreditsAmountDecimal(CreditsAmount{ amount })); + return MakeTableValueWithTooltip( + table, + std::move(tooltip), + price, + tr::lng_auction_average_tooltip( + tr::now, + lt_amount, + tr::bold( + Ui::Text::IconEmoji(&st::starIconEmojiInline).append( + Lang::FormatCountDecimal(amount))), + lt_gift, + tr::bold(name), + tr::marked), + helper.context()); +} + +[[nodiscard]] object_ptr AuctionInfoTable( + not_null parent, + not_null container, + rpl::producer value) { + auto result = object_ptr(parent.get(), st::defaultTable); + const auto raw = result.data(); + + struct State { + rpl::variable value; + rpl::variable finished; + }; + const auto state = raw->lifetime().make_state(); + state->value = std::move(value); + + const auto &now = state->value.current(); + const auto name = now.gift->resellTitle; + state->finished = now.finished() + ? (rpl::single(true) | rpl::type_erased()) + : (MinutesLeftTillValue(now.endDate) | rpl::map(!rpl::mappers::_1)); + + const auto date = [&](TimeId time) { + return rpl::single( + tr::marked(langDateTime(base::unixtime::parse(time)))); + }; + AddTableRow( + raw, + rpl::conditional( + state->finished.value(), + tr::lng_gift_link_label_first_sale(), + tr::lng_auction_start_label()), + date(now.startDate)); + AddTableRow( + raw, + rpl::conditional( + state->finished.value(), + 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{ .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( + 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; +} + +void AuctionAboutBox( + not_null box, + int rounds, + int giftsPerRound) { + +} + +void AuctionInfoBox( + not_null box, + std::shared_ptr show, + not_null peer, + rpl::producer value) { + using namespace Info::PeerGifts; + + struct State { + explicit State(not_null session) + : delegate(session, GiftButtonMode::Minimal) { + } + + Delegate delegate; + rpl::variable value; + rpl::variable minutesLeft; + }; + const auto state = box->lifetime().make_state(&show->session()); + state->value = std::move(value); + state->minutesLeft = MinutesLeftTillValue( + state->value.current().endDate); + + box->setStyle(st::giftBox); + + const auto name = state->value.current().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(box, size.height()), + st::auctionInfoPreviewMargin); + const auto gift = CreateChild(preview, &state->delegate); + gift->setAttribute(Qt::WA_TransparentForMouseEvents); + gift->setDescriptor(GiftTypeStars{ + .info = *state->value.current().gift, + }, GiftButtonMode::Minimal); + + 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; + 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( + tr::now, + lt_arrow, + Text::IconEmoji(&st::textMoreIconEmoji), + tr::link), + tr::rich); + }); + box->addRow( + object_ptr( + box, + name, + st::uniqueGiftTitle), + style::al_top); + const auto about = box->addRow( + object_ptr( + box, + std::move(aboutText), + st::uniqueGiftSubtitle), + st::boxRowPadding + QMargins(0, st::auctionInfoSubtitleSkip, 0, 0), + style::al_top); + about->setTryMakeSimilarLines(true); + box->resizeToWidth(box->widthNoMargins()); + + about->setClickHandlerFilter([=](const auto &...) { + show->show(Box(AuctionAboutBox, rounds, perRound)); + return false; + }); + + box->addRow( + AuctionInfoTable(box, box->verticalLayout(), state->value.value()), + st::boxRowPadding + st::auctionInfoTableMargin); + + const auto button = box->addButton(rpl::single(QString()), [=] { + if (state->value.current().finished() || !state->minutesLeft.current()) { + box->closeBox(); + return; + } + + const auto save = [=](int amount) { + const auto &now = state->value.current(); + const auto was = (now.my.bid > 0); + const auto perRound = now.gift->auctionGiftsPerRound; + const auto done = [=](Payments::CheckoutResult result) { + if (result == Payments::CheckoutResult::Paid) { + show->showToast({ + .title = (was + ? tr::lng_auction_bid_increased_title + : tr::lng_auction_bid_placed_title)( + tr::now), + .text = tr::lng_auction_bid_done_text( + tr::now, + lt_count, + perRound, + tr::rich) + }); + } + }; + PlaceAuctionBid( + show, + peer, + amount, + now, + done); + }; + const auto bidBox = show->show(MakeAuctionBidBox({ + .show = show, + .state = state->value.value(), + .save = save, + })); + bidBox->boxClosing( + ) | rpl::start_with_next([=] { + box->closeBox(); + }, box->lifetime()); + }); + + auto buttonTitle = rpl::combine( + state->value.value(), + state->minutesLeft.value() + ) | rpl::map([=](const Data::GiftAuctionState &state, int minutes) { + return (state.finished() || minutes <= 0) + ? tr::lng_box_ok(tr::marked) + : tr::lng_auction_join_button(tr::marked); + }) | rpl::flatten_latest(); + + auto buttonSubtitle = rpl::combine( + state->value.value(), + state->minutesLeft.value() + ) | rpl::map([=](const Data::GiftAuctionState &state, int minutes) { + if (state.finished() || minutes <= 0) { + return rpl::single(TextWithEntities()); + } + const auto hours = (minutes / 60); + minutes -= (hours * 60); + + auto value = [](int count) { + return rpl::single(tr::marked(QString::number(count))); + }; + return tr::lng_auction_join_time_left( + lt_time, + (hours + ? tr::lng_auction_join_time_medium( + lt_hours, + value(hours), + lt_minutes, + value(minutes), + tr::marked) + : tr::lng_auction_join_time_small( + lt_minutes, + value(minutes), + tr::marked)), + tr::marked); + }) | rpl::flatten_latest(); + + SetButtonTwoLabels( + button, + std::move(buttonTitle), + std::move(buttonSubtitle), + st::resaleButtonTitle, + st::resaleButtonSubtitle); + + rpl::combine( + state->value.value(), + state->minutesLeft.value() + ) | rpl::start_with_next([=]( + const Data::GiftAuctionState &state, + int minutes) { + about->setTextColorOverride((state.finished() || minutes <= 0) + ? st::attentionButtonFg->c + : std::optional()); + }, box->lifetime()); +} + +} // namespace + +rpl::lifetime ShowStarGiftAuction( + not_null controller, + not_null peer, + QString slug, + Fn finishRequesting, + Fn boxClosed) { + const auto weak = base::make_weak(controller); + const auto session = &controller->session(); + const auto value = std::make_shared< + rpl::variable + >(); + return session->giftAuctions().state( + slug + ) | rpl::start_with_next([=](Data::GiftAuctionState &&state) { + if (const auto onstack = finishRequesting) { + onstack(); + } + const auto initial = !value->current().gift.has_value(); + (*value) = std::move(state); + if (initial) { + if (const auto strong = weak.get()) { + const auto box = strong->show(Box( + AuctionInfoBox, + strong->uiShow(), + peer, + value->value())); + box->boxClosing( + ) | rpl::start_with_next(boxClosed, box->lifetime()); + } + } + }); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/boxes/star_gift_auction_box.h b/Telegram/SourceFiles/boxes/star_gift_auction_box.h new file mode 100644 index 0000000000..ffa3147ab0 --- /dev/null +++ b/Telegram/SourceFiles/boxes/star_gift_auction_box.h @@ -0,0 +1,23 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Window { +class SessionController; +} // namespace Window + +namespace Ui { + +[[nodiscard]] rpl::lifetime ShowStarGiftAuction( + not_null controller, + not_null peer, + QString slug, + Fn finishRequesting, + Fn boxClosed); + +} // namespace Ui diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index d0de8d1832..36a48e1a01 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peer_list_controllers.h" #include "boxes/premium_preview_box.h" #include "boxes/send_credits_box.h" +#include "boxes/star_gift_auction_box.h" #include "boxes/star_gift_resale_box.h" #include "boxes/transfer_gift_box.h" #include "chat_helpers/emoji_suggestions_widget.h" @@ -2039,17 +2040,20 @@ void SendGiftBox( id, star->info.resellTitle, [=] { state->resaleRequestingId = 0; }); - } else if (star && star->info.auction) { + } else if (star && star->info.auction()) { const auto id = star->info.id; if (state->resaleRequestingId == id) { return; } state->resaleRequestingId = id; - //state->resaleLifetime = ShowStarGiftAuction( - // window, - // peer, - // star->info.auctionSlug, - // [=] { state->resaleRequestingId = 0; }); + state->resaleLifetime = ShowStarGiftAuction( + window, + peer, + star->info.auctionSlug, + [=] { state->resaleRequestingId = 0; }, + crl::guard(raw, [=] { + state->resaleLifetime.destroy(); + })); } else if (star && IsSoldOut(star->info)) { window->show(Box(SoldOutBox, window, *star)); } else if (star diff --git a/Telegram/SourceFiles/data/components/gift_auctions.cpp b/Telegram/SourceFiles/data/components/gift_auctions.cpp index 92a495046d..16c5774a26 100644 --- a/Telegram/SourceFiles/data/components/gift_auctions.cpp +++ b/Telegram/SourceFiles/data/components/gift_auctions.cpp @@ -7,15 +7,190 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/components/gift_auctions.h" +#include "api/api_premium.h" +#include "apiwrap.h" #include "data/data_session.h" #include "main/main_session.h" namespace Data { GiftAuctions::GiftAuctions(not_null session) -: _session(session) { +: _session(session) +, _timer([=] { checkSubscriptions(); }) { } GiftAuctions::~GiftAuctions() = default; +rpl::producer GiftAuctions::state(const QString &slug) { + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + + auto &entry = _map[slug]; + if (!entry) { + entry = std::make_unique(); + } + const auto raw = entry.get(); + + raw->changes.events() | rpl::start_with_next([=] { + consumer.put_next_copy(raw->state); + }, lifetime); + + const auto now = crl::now(); + if (raw->state.subscribedTill < 0 + || raw->state.subscribedTill >= now) { + consumer.put_next_copy(raw->state); + } else if (raw->state.subscribedTill >= 0) { + request(slug); + } + + return lifetime; + }; +} + +void GiftAuctions::apply(const MTPDupdateStarGiftAuctionState &data) { + if (const auto entry = find(data.vgift_id().v)) { + apply(entry, data.vstate()); + } +} + +void GiftAuctions::apply(const MTPDupdateStarGiftAuctionUserState &data) { + if (const auto entry = find(data.vgift_id().v)) { + apply(entry, data.vuser_state()); + } +} + +void GiftAuctions::checkSubscriptions() { + const auto now = crl::now(); + auto next = crl::time(); + for (const auto &[slug, entry] : _map) { + const auto raw = entry.get(); + const auto till = raw->state.subscribedTill; + if (till <= 0 || !raw->changes.has_consumers()) { + continue; + } else if (till <= now) { + request(slug); + } else { + const auto timeout = till - now; + if (!next || timeout < next) { + next = timeout; + } + } + } + if (next) { + _timer.callOnce(next); + } +} + +void GiftAuctions::request(const QString &slug) { + auto &entry = _map[slug]; + Assert(entry != nullptr); + + const auto raw = entry.get(); + if (raw->requested) { + return; + } + raw->requested = true; + _session->api().request(MTPpayments_GetStarGiftAuctionState( + MTP_inputStarGiftAuctionSlug(MTP_string(slug)), + MTP_int(raw->state.version) + )).done([=](const MTPpayments_StarGiftAuctionState &result) { + raw->requested = false; + const auto &data = result.data(); + + raw->state.gift = Api::FromTL(_session, data.vgift()); + if (!raw->state.gift) { + return; + } + const auto timeout = data.vtimeout().v; + const auto ms = timeout * crl::time(1000); + raw->state.subscribedTill = ms ? (crl::now() + ms) : -1; + + _session->data().processUsers(data.vusers()); + + apply(raw, data.vstate()); + apply(raw, data.vuser_state()); + if (raw->changes.has_consumers()) { + raw->changes.fire({}); + if (ms && (!_timer.isActive() || _timer.remainingTime() > ms)) { + _timer.callOnce(ms); + } + } + }).send(); +} + +GiftAuctions::Entry *GiftAuctions::find(uint64 giftId) const { + for (const auto &[slug, entry] : _map) { + if (entry->state.gift && entry->state.gift->id == giftId) { + return entry.get(); + } + } + return nullptr; +} + +void GiftAuctions::apply( + not_null entry, + const MTPStarGiftAuctionState &state) { + Expects(entry->state.gift.has_value()); + + const auto raw = &entry->state; + state.match([&](const MTPDstarGiftAuctionState &data) { + const auto version = data.vversion().v; + if (raw->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; + const auto &levels = data.vbid_levels().v; + raw->bidLevels.clear(); + raw->bidLevels.reserve(levels.size()); + for (const auto &level : levels) { + auto &entry = raw->bidLevels.emplace_back(); + const auto &data = level.data(); + entry.amount = data.vamount().v; + entry.position = data.vpos().v; + entry.date = data.vdate().v; + } + const auto &top = data.vtop_bidders().v; + raw->topBidders.clear(); + raw->topBidders.reserve(top.size()); + for (const auto &user : top) { + raw->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; + }, [&](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 + = 0; + }, [&](const MTPDstarGiftAuctionStateNotModified &data) { + }); +} + +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() + ? _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(); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/components/gift_auctions.h b/Telegram/SourceFiles/data/components/gift_auctions.h index a241711eb6..1f41e3ee89 100644 --- a/Telegram/SourceFiles/data/components/gift_auctions.h +++ b/Telegram/SourceFiles/data/components/gift_auctions.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/timer.h" #include "data/data_star_gift.h" namespace Main { @@ -31,7 +32,7 @@ struct StarGiftAuctionMyState { }; struct GiftAuctionState { - StarGift gift; + std::optional gift; StarGiftAuctionMyState my; std::vector bidLevels; std::vector> topBidders; @@ -45,6 +46,10 @@ struct GiftAuctionState { int totalRounds = 0; int giftsLeft = 0; int version = 0; + + [[nodiscard]] bool finished() const { + return (averagePrice != 0); + } }; class GiftAuctions final { @@ -52,12 +57,32 @@ public: explicit GiftAuctions(not_null session); ~GiftAuctions(); - //[[nodiscard]] rpl::producer< + [[nodiscard]] rpl::producer state(const QString &slug); + + void apply(const MTPDupdateStarGiftAuctionState &data); + void apply(const MTPDupdateStarGiftAuctionUserState &data); private: + struct Entry { + GiftAuctionState state; + rpl::event_stream<> changes; + bool requested = false; + }; + + void request(const QString &slug); + Entry *find(uint64 giftId) const; + void apply( + not_null entry, + const MTPStarGiftAuctionState &state); + void apply( + not_null entry, + const MTPStarGiftAuctionUserState &state); + void checkSubscriptions(); + const not_null _session; - base::flat_map> _map; + base::Timer _timer; + base::flat_map> _map; }; diff --git a/Telegram/SourceFiles/data/data_star_gift.h b/Telegram/SourceFiles/data/data_star_gift.h index 143a013967..e2a7aa6ac6 100644 --- a/Telegram/SourceFiles/data/data_star_gift.h +++ b/Telegram/SourceFiles/data/data_star_gift.h @@ -109,9 +109,10 @@ struct StarGift { int64 starsResellMin = 0; not_null document; PeerData *releasedBy = nullptr; - QString auctionSlug; QString resellTitle; int resellCount = 0; + QString auctionSlug; + int auctionGiftsPerRound = 0; int limitedLeft = 0; int limitedCount = 0; int perUserTotal = 0; @@ -124,9 +125,12 @@ struct StarGift { bool peerColorAvailable : 1 = false; bool upgradable : 1 = false; bool birthday : 1 = false; - bool auction : 1 = false; bool soldOut : 1 = false; + [[nodiscard]] bool auction() const { + return !auctionSlug.isEmpty(); + } + friend inline bool operator==( const StarGift &, const StarGift &) = default; diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp index cad0c546d2..c1315ab538 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp @@ -277,7 +277,7 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) { ? tr::lng_gift_transfer_button( tr::now, Ui::Text::WithEntities) - : (data.info.auction && !data.info.soldOut) + : (data.info.auction() && !data.info.soldOut) ? tr::lng_gift_stars_auction_join( tr::now, Ui::Text::WithEntities) @@ -646,7 +646,7 @@ void GiftButton::paintEvent(QPaintEvent *e) { const auto auction = stargift && !stargift->userpic && !stargift->info.unique - && stargift->info.auction; + && stargift->info.auction(); const auto hidden = stargift && stargift->hidden; const auto soldOut = stargift && !(stargift->pinned || stargift->pinnedSelection) @@ -807,7 +807,7 @@ void GiftButton::paintEvent(QPaintEvent *e) { ? ('#' + QString::number(unique->number)) : data.resale ? tr::lng_gift_stars_resale(tr::now) - : (!unique && data.info.auction) + : (!unique && data.info.auction()) ? tr::lng_gift_stars_auction(tr::now) : soldOut ? tr::lng_gift_stars_sold_out(tr::now) @@ -838,7 +838,7 @@ void GiftButton::paintEvent(QPaintEvent *e) { : soldOut ? st::attentionButtonFg->c : (!data.userpic - && (data.info.auction || data.info.requirePremium)) + && (data.info.auction() || data.info.requirePremium)) ? st::creditsFg->c : st::windowActiveTextFg->c), .bg2 = (onsale diff --git a/Telegram/SourceFiles/lang/lang_text_entity.h b/Telegram/SourceFiles/lang/lang_text_entity.h index b1204ba1e9..dae0bc5db9 100644 --- a/Telegram/SourceFiles/lang/lang_text_entity.h +++ b/Telegram/SourceFiles/lang/lang_text_entity.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "ui/text/text_entity.h" +#include "ui/text/text_utilities.h" namespace Lang { @@ -32,3 +33,148 @@ struct ReplaceTag { }; } // namespace Lang + +namespace tr { +namespace details { + +struct MarkedProjection { + [[nodiscard]] TextWithEntities operator()(const QString &value) const { + return TextWithEntities{ value }; + } + [[nodiscard]] TextWithEntities operator()(QString &&value) const { + return TextWithEntities{ std::move(value) }; + } +}; + +struct RichProjection { + [[nodiscard]] TextWithEntities operator()(const QString &value) const { + return Ui::Text::RichLangValue(value); + } +}; + +struct BoldProjection { + [[nodiscard]] TextWithEntities operator()(const QString &value) const { + return Ui::Text::Bold(value); + } + [[nodiscard]] TextWithEntities operator()( + const TextWithEntities &value) const { + return Ui::Text::Wrapped(value, EntityType::Bold); + } + [[nodiscard]] TextWithEntities operator()( + TextWithEntities &&value) const { + return Ui::Text::Wrapped(std::move(value), EntityType::Bold); + } +}; + +struct SemiboldProjection { + [[nodiscard]] TextWithEntities operator()(const QString &value) const { + return Ui::Text::Semibold(value); + } + [[nodiscard]] TextWithEntities operator()( + const TextWithEntities &value) const { + return Ui::Text::Wrapped(value, EntityType::Semibold); + } + [[nodiscard]] TextWithEntities operator()( + TextWithEntities &&value) const { + return Ui::Text::Wrapped(std::move(value), EntityType::Semibold); + } +}; + +struct ItalicProjection { + [[nodiscard]] TextWithEntities operator()(const QString &value) const { + return Ui::Text::Italic(value); + } + [[nodiscard]] TextWithEntities operator()( + const TextWithEntities &value) const { + return Ui::Text::Wrapped(value, EntityType::Italic); + } + [[nodiscard]] TextWithEntities operator()( + TextWithEntities &&value) const { + return Ui::Text::Wrapped(std::move(value), EntityType::Italic); + } +}; + +struct UnderlineProjection { + [[nodiscard]] TextWithEntities operator()(const QString &value) const { + return Ui::Text::Underline(value); + } + [[nodiscard]] TextWithEntities operator()( + const TextWithEntities &value) const { + return Ui::Text::Wrapped(value, EntityType::Underline); + } + [[nodiscard]] TextWithEntities operator()( + TextWithEntities &&value) const { + return Ui::Text::Wrapped(std::move(value), EntityType::Underline); + } +}; + +struct StrikeOutProjection { + [[nodiscard]] TextWithEntities operator()(const QString &value) const { + return Ui::Text::StrikeOut(value); + } + [[nodiscard]] TextWithEntities operator()( + const TextWithEntities &value) const { + return Ui::Text::Wrapped(value, EntityType::StrikeOut); + } + [[nodiscard]] TextWithEntities operator()( + TextWithEntities &&value) const { + return Ui::Text::Wrapped(std::move(value), EntityType::StrikeOut); + } +}; + +struct LinkProjection { + [[nodiscard]] TextWithEntities operator()(const QString &value) const { + return Ui::Text::Link(value); + } + [[nodiscard]] TextWithEntities operator()( + const QString &value, + const QString &url) const { + return Ui::Text::Link(value, url); + } + [[nodiscard]] TextWithEntities operator()( + const QString &value, + int index) const { + return Ui::Text::Link(value, index); + } + [[nodiscard]] TextWithEntities operator()( + const TextWithEntities &value) const { + return Ui::Text::Link(value); + } + [[nodiscard]] TextWithEntities operator()( + const TextWithEntities &value, + const QString &url) const { + return Ui::Text::Link(value, url); + } + [[nodiscard]] TextWithEntities operator()( + const TextWithEntities &value, + int index) const { + return Ui::Text::Link(value, index); + } + [[nodiscard]] TextWithEntities operator()( + TextWithEntities &&value) const { + return Ui::Text::Link(std::move(value)); + } + [[nodiscard]] TextWithEntities operator()( + TextWithEntities &&value, + const QString &url) const { + return Ui::Text::Link(std::move(value), url); + } + [[nodiscard]] TextWithEntities operator()( + TextWithEntities &&value, + int index) const { + return Ui::Text::Link(std::move(value), index); + } +}; + +} // namespace details + +inline constexpr details::MarkedProjection marked{}; +inline constexpr details::RichProjection rich{}; +inline constexpr details::BoldProjection bold{}; +inline constexpr details::SemiboldProjection semibold{}; +inline constexpr details::ItalicProjection italic{}; +inline constexpr details::UnderlineProjection underline{}; +inline constexpr details::StrikeOutProjection strikeout{}; +inline constexpr details::LinkProjection link{}; + +} // namespace tr diff --git a/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp b/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp index 69d60f6200..1192ecc8a0 100644 --- a/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp @@ -591,6 +591,7 @@ void PaidReactionsBox( const auto colorings = args.colorings; const auto videoStreamChoosing = args.videoStreamChoosing; const auto videoStreamSending = args.videoStreamSending; + const auto giftAuction = args.giftAuction; const auto videoStream = videoStreamChoosing || videoStreamSending; const auto initialShownPeer = ranges::find( args.top, @@ -636,7 +637,7 @@ void PaidReactionsBox( nullptr, &st::paidReactBubbleIcon, st::boxRowPadding); - if (videoStream) { + if (videoStream || giftAuction) { state->chosen.value() | rpl::start_with_next([=](int count) { bubble->setBrushOverride(activeFgOverride(count)); }, bubble->lifetime()); @@ -653,7 +654,7 @@ void PaidReactionsBox( args.chosen, args.max, changed, - videoStream ? activeFgOverride : Fn()); + (videoStream || giftAuction) ? activeFgOverride : Fn()); box->addTopButton( dark ? st::darkEditStarsClose : st::boxTitleClose, @@ -677,6 +678,7 @@ void PaidReactionsBox( box->addRow( VideoStreamStarsLevel(box, colorings, state->chosen.value()), st::boxRowPadding + QMargins(0, st::paidReactTitleSkip, 0, 0)); + } else if (giftAuction) { } else if (videoStreamSending) { addTopReactors(); } @@ -688,45 +690,48 @@ void PaidReactionsBox( ? tr::lng_paid_comment_title() : videoStreamSending ? tr::lng_paid_reaction_title() + : giftAuction + ? tr::lng_auction_bid_title() : tr::lng_paid_react_title()), dark ? st::darkEditStarsCenteredTitle : st::boostCenteredTitle), st::boxRowPadding + QMargins(0, st::paidReactTitleSkip, 0, 0), style::al_top); - const auto labelWrap = box->addRow( - object_ptr(box), - (st::boxRowPadding - + QMargins(0, st::lineWidth, 0, st::boostBottomSkip))); - const auto label = CreateChild( - labelWrap, - (videoStream - ? (videoStreamChoosing - ? tr::lng_paid_comment_about - : tr::lng_paid_reaction_about)( - lt_name, - rpl::single(Text::Bold(args.name)), + if (!giftAuction) { + const auto labelWrap = box->addRow( + object_ptr(box), + (st::boxRowPadding + + QMargins(0, st::lineWidth, 0, st::boostBottomSkip))); + const auto label = CreateChild( + labelWrap, + (videoStream + ? (videoStreamChoosing + ? tr::lng_paid_comment_about + : tr::lng_paid_reaction_about)( + lt_name, + rpl::single(Text::Bold(args.name)), + Text::RichLangValue) + : already + ? tr::lng_paid_react_already( + lt_count, + rpl::single(already) | tr::to_count(), Text::RichLangValue) - : already - ? tr::lng_paid_react_already( - lt_count, - rpl::single(already) | tr::to_count(), - Text::RichLangValue) - : tr::lng_paid_react_about( - lt_channel, - rpl::single(Text::Bold(args.name)), - Text::RichLangValue)), - dark ? st::darkEditStarsText : st::boostText); - label->setTryMakeSimilarLines(true); - labelWrap->widthValue() | rpl::start_with_next([=](int width) { - label->resizeToWidth(width); - }, label->lifetime()); - label->heightValue() | rpl::start_with_next([=](int height) { - const auto min = 2 * st::normalFont->height; - const auto skip = std::max((min - height) / 2, 0); - labelWrap->resize(labelWrap->width(), 2 * skip + height); - label->moveToLeft(0, skip); - }, label->lifetime()); - - if (!videoStream) { + : tr::lng_paid_react_about( + lt_channel, + rpl::single(Text::Bold(args.name)), + Text::RichLangValue)), + dark ? st::darkEditStarsText : st::boostText); + label->setTryMakeSimilarLines(true); + labelWrap->widthValue() | rpl::start_with_next([=](int width) { + label->resizeToWidth(width); + }, label->lifetime()); + label->heightValue() | rpl::start_with_next([=](int height) { + const auto min = 2 * st::normalFont->height; + const auto skip = std::max((min - height) / 2, 0); + labelWrap->resize(labelWrap->width(), 2 * skip + height); + label->moveToLeft(0, skip); + }, label->lifetime()); + } + if (!videoStream && !giftAuction) { addTopReactors(); const auto skip = st::defaultCheckbox.margin.bottom(); @@ -747,19 +752,21 @@ void PaidReactionsBox( AddSkip(content); AddSkip(content); - AddDividerText( - content, - tr::lng_paid_react_agree( - lt_link, - rpl::combine( - tr::lng_paid_react_agree_link(), - tr::lng_group_invite_subscription_about_url() - ) | rpl::map([](const QString &text, const QString &url) { - return Ui::Text::Link(text, url); - }), - Ui::Text::RichLangValue), - st::defaultBoxDividerLabelPadding, - dark ? st::groupCallDividerLabel : st::defaultDividerLabel); + if (!giftAuction) { + AddDividerText( + content, + tr::lng_paid_react_agree( + lt_link, + rpl::combine( + tr::lng_paid_react_agree_link(), + tr::lng_group_invite_subscription_about_url() + ) | rpl::map([](const QString &text, const QString &url) { + return Ui::Text::Link(text, url); + }), + Ui::Text::RichLangValue), + st::defaultBoxDividerLabelPadding, + dark ? st::groupCallDividerLabel : st::defaultDividerLabel); + } const auto button = box->addButton(rpl::single(QString()), [=] { args.send(state->chosen.current(), state->shownPeer.current()); diff --git a/Telegram/SourceFiles/payments/ui/payments_reaction_box.h b/Telegram/SourceFiles/payments/ui/payments_reaction_box.h index be68724f6d..af63a8c219 100644 --- a/Telegram/SourceFiles/payments/ui/payments_reaction_box.h +++ b/Telegram/SourceFiles/payments/ui/payments_reaction_box.h @@ -48,6 +48,7 @@ struct PaidReactionBoxArgs { Fn send; bool videoStreamChoosing = false; bool videoStreamSending = false; + bool giftAuction = false; bool dark = false; }; diff --git a/Telegram/SourceFiles/ui/controls/button_labels.cpp b/Telegram/SourceFiles/ui/controls/button_labels.cpp index 8a3f9783af..13563d61bf 100644 --- a/Telegram/SourceFiles/ui/controls/button_labels.cpp +++ b/Telegram/SourceFiles/ui/controls/button_labels.cpp @@ -7,12 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/controls/button_labels.h" +#include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" namespace Ui { void SetButtonTwoLabels( - not_null button, + not_null button, rpl::producer title, rpl::producer subtitle, const style::FlatLabel &st, @@ -25,9 +26,12 @@ void SetButtonTwoLabels( buttonTitle->show(); const auto buttonSubtitle = Ui::CreateChild( button, - std::move(subtitle), + rpl::duplicate( + subtitle + ) | rpl::filter([](const TextWithEntities &text) { + return !text.empty(); + }), subst); - buttonSubtitle->show(); buttonSubtitle->setOpacity(0.6); if (textFg) { buttonTitle->setTextColorOverride((*textFg)->c); @@ -40,10 +44,20 @@ void SetButtonTwoLabels( rpl::combine( button->sizeValue(), buttonTitle->sizeValue(), - buttonSubtitle->sizeValue() - ) | rpl::start_with_next([=](QSize outer, QSize title, QSize subtitle) { + buttonSubtitle->sizeValue(), + std::move(subtitle) + ) | rpl::start_with_next([=]( + QSize outer, + QSize title, + QSize subtitle, + const TextWithEntities &subtitleText) { + const auto withSubtitle = !subtitleText.empty(); + buttonSubtitle->setVisible(withSubtitle); + const auto two = title.height() + subtitle.height(); - const auto titleTop = (outer.height() - two) / 2; + const auto titleTop = withSubtitle + ? (outer.height() - two) / 2 + : button->st().textTop; const auto subtitleTop = titleTop + title.height(); buttonTitle->moveToLeft( (outer.width() - title.width()) / 2, diff --git a/Telegram/SourceFiles/ui/controls/button_labels.h b/Telegram/SourceFiles/ui/controls/button_labels.h index 5de559a96c..4b746a46b1 100644 --- a/Telegram/SourceFiles/ui/controls/button_labels.h +++ b/Telegram/SourceFiles/ui/controls/button_labels.h @@ -13,10 +13,10 @@ struct FlatLabel; namespace Ui { -class RpWidget; +class RoundButton; void SetButtonTwoLabels( - not_null button, + not_null button, rpl::producer title, rpl::producer subtitle, const style::FlatLabel &st, diff --git a/Telegram/SourceFiles/ui/controls/table_rows.cpp b/Telegram/SourceFiles/ui/controls/table_rows.cpp new file mode 100644 index 0000000000..75318df040 --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/table_rows.cpp @@ -0,0 +1,392 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/controls/table_rows.h" + +#include "base/event_filter.h" +#include "base/timer_rpl.h" +#include "boxes/peers/prepare_short_info_box.h" +#include "chat_helpers/compose/compose_show.h" +#include "data/data_session.h" +#include "data/data_peer.h" +#include "info/profile/info_profile_badge.h" +#include "info/profile/info_profile_values.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "ui/controls/userpic_button.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/tooltip.h" +#include "ui/wrap/table_layout.h" +#include "ui/empty_userpic.h" +#include "ui/rect.h" +#include "ui/rp_widget.h" +#include "ui/ui_utility.h" +#include "styles/style_boxes.h" +#include "styles/style_credits.h" +#include "styles/style_giveaway.h" +#include "styles/style_info.h" +#include "styles/style_layers.h" + +namespace Ui { +namespace { + +constexpr auto kTooltipDuration = 6 * crl::time(1000); + +} // namespace + +void AddTableRow( + not_null table, + rpl::producer label, + object_ptr value, + style::margins valueMargins) { + table->addRow( + (label + ? object_ptr( + table, + std::move(label), + table->st().defaultLabel) + : object_ptr(nullptr)), + std::move(value), + st::giveawayGiftCodeLabelMargin, + valueMargins); +} + +not_null AddTableRow( + not_null table, + rpl::producer label, + rpl::producer value, + const Text::MarkedContext &context) { + auto widget = object_ptr( + table, + std::move(value), + table->st().defaultValue, + st::defaultPopupMenu, + context); + const auto result = widget.data(); + AddTableRow(table, std::move(label), std::move(widget)); + return result; +} + +void AddTableRow( + not_null table, + rpl::producer label, + std::shared_ptr show, + PeerId id) { + if (!id) { + return; + } + AddTableRow( + table, + std::move(label), + MakePeerTableValue(table, show, id), + st::giveawayGiftCodePeerMargin); +} + +object_ptr MakeValueWithSmallButton( + not_null table, + not_null value, + rpl::producer buttonText, + Fn button)> handler, + int topSkip) { + class MarginedWidget final : public RpWidget { + public: + using RpWidget::RpWidget; + QMargins getMargins() const override { + return { 0, 0, 0, st::giveawayGiftCodePeerMargin.bottom() }; + } + }; + auto result = object_ptr(table); + const auto raw = result.data(); + + value->setParent(raw); + value->show(); + + const auto button = CreateChild( + raw, + std::move(buttonText), + table->st().smallButton); + button->setTextTransform(RoundButton::TextTransform::NoTransform); + if (handler) { + button->setClickedCallback([button, handler = std::move(handler)] { + handler(button); + }); + } else { + button->setAttribute(Qt::WA_TransparentForMouseEvents); + } + rpl::combine( + raw->widthValue(), + button->widthValue(), + value->naturalWidthValue() + ) | rpl::start_with_next([=](int width, int buttonWidth, int) { + const auto buttonSkip = st::normalFont->spacew + buttonWidth; + value->resizeToNaturalWidth(width - buttonSkip); + value->moveToLeft(0, 0, width); + button->moveToLeft( + rect::right(value) + st::normalFont->spacew, + (topSkip + + (table->st().defaultValue.style.font->ascent + - table->st().smallButton.style.font->ascent)), + width); + }, value->lifetime()); + + value->heightValue() | rpl::start_with_next([=](int height) { + const auto bottom = st::giveawayGiftCodePeerMargin.bottom(); + raw->resize(raw->width(), height + bottom); + }, raw->lifetime()); + + return result; +} + +object_ptr MakePeerTableValue( + not_null table, + std::shared_ptr show, + PeerId id, + rpl::producer button, + Fn handler) { + auto result = object_ptr(table); + const auto raw = result.data(); + + const auto &st = st::giveawayGiftCodeUserpic; + raw->resize(raw->width(), st.photoSize); + + const auto peer = show->session().data().peer(id); + const auto userpic = CreateChild(raw, peer, st); + const auto label = CreateChild( + raw, + (button && handler) ? peer->shortName() : peer->name(), + table->st().defaultValue); + + raw->widthValue() | rpl::start_with_next([=](int width) { + const auto position = st::giveawayGiftCodeNamePosition; + label->resizeToNaturalWidth(width - position.x()); + label->moveToLeft(position.x(), position.y(), width); + const auto top = (raw->height() - userpic->height()) / 2; + userpic->moveToLeft(0, top, width); + }, label->lifetime()); + + label->naturalWidthValue() | rpl::start_with_next([=](int width) { + raw->setNaturalWidth(st::giveawayGiftCodeNamePosition.x() + width); + }, label->lifetime()); + userpic->setAttribute(Qt::WA_TransparentForMouseEvents); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + label->setTextColorOverride(table->st().defaultValue.palette.linkFg->c); + + raw->setClickedCallback([=] { + show->showBox(PrepareShortInfoBox(peer, show)); + }); + + if (!button || !handler) { + return result; + } + return MakeValueWithSmallButton( + table, + result.release(), + std::move(button), + [=](not_null button) { handler(); }, + st::giveawayGiftCodeNamePosition.y()); +} + +object_ptr MakePeerWithStatusValue( + not_null table, + std::shared_ptr show, + PeerId id, + Fn, EmojiStatusId)> pushStatusId) { + auto result = object_ptr(table); + const auto raw = result.data(); + + const auto peerLabel = MakePeerTableValue(table, show, id).release(); + peerLabel->setParent(raw); + peerLabel->show(); + + raw->resize(raw->width(), peerLabel->height()); + + using namespace Info::Profile; + struct State { + rpl::variable content; + }; + const auto peer = show->session().data().peer(id); + const auto state = peerLabel->lifetime().make_state(); + state->content = EmojiStatusIdValue( + peer + ) | rpl::map([=](EmojiStatusId emojiStatusId) { + if (!peer->session().premium() + || (!peer->isSelf() && !emojiStatusId)) { + return Badge::Content(); + } + return Badge::Content{ + .badge = BadgeType::Premium, + .emojiStatusId = emojiStatusId, + }; + }); + const auto badge = peerLabel->lifetime().make_state( + raw, + st::infoPeerBadge, + &peer->session(), + state->content.value(), + nullptr, + [=] { return show->paused(ChatHelpers::PauseReason::Layer); }); + state->content.value( + ) | rpl::start_with_next([=](const Badge::Content &content) { + if (const auto widget = badge->widget()) { + pushStatusId(widget, content.emojiStatusId); + } + }, raw->lifetime()); + + rpl::combine( + raw->widthValue(), + rpl::single(rpl::empty) | rpl::then(badge->updated()) + ) | rpl::start_with_next([=](int width, const auto &) { + const auto badgeWidget = badge->widget(); + const auto badgeSkip = badgeWidget + ? (st::normalFont->spacew + badgeWidget->width()) + : 0; + peerLabel->resizeToNaturalWidth(width - badgeSkip); + peerLabel->moveToLeft(0, 0, width); + if (badgeWidget) { + badgeWidget->moveToLeft( + peerLabel->width() + st::normalFont->spacew, + st::giftBoxByStarsStarTop, + width); + } + }, raw->lifetime()); + + return result; +} + +object_ptr MakeHiddenPeerTableValue( + not_null table) { + auto result = object_ptr(table); + const auto raw = result.data(); + + const auto &st = st::giveawayGiftCodeUserpic; + raw->resize(raw->width(), st.photoSize); + + const auto userpic = CreateChild(raw); + const auto usize = st.photoSize; + userpic->resize(usize, usize); + userpic->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(userpic); + EmptyUserpic::PaintHiddenAuthor(p, 0, 0, usize, usize); + }, userpic->lifetime()); + + const auto label = CreateChild( + raw, + tr::lng_gift_from_hidden(), + table->st().defaultValue); + raw->widthValue( + ) | rpl::start_with_next([=](int width) { + const auto position = st::giveawayGiftCodeNamePosition; + label->resizeToNaturalWidth(width - position.x()); + label->moveToLeft(position.x(), position.y(), width); + const auto top = (raw->height() - userpic->height()) / 2; + userpic->moveToLeft(0, top, width); + }, label->lifetime()); + + userpic->setAttribute(Qt::WA_TransparentForMouseEvents); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + label->setTextColorOverride(st::windowFg->c); + + return result; +} + +void ShowTableRowTooltip( + std::shared_ptr data, + not_null target, + rpl::producer text, + int duration, + const Text::MarkedContext &context) { + if (data->raw) { + data->raw->toggleAnimated(false); + } + const auto parent = data->parent; + const auto tooltip = CreateChild( + parent, + MakeNiceTooltipLabel( + parent, + std::move(text), + st::boxWideWidth, + st::defaultImportantTooltipLabel, + st::defaultPopupMenu, + context), + st::defaultImportantTooltip); + tooltip->toggleFast(false); + + base::install_event_filter(tooltip, qApp, [=](not_null e) { + if (e->type() == QEvent::MouseButtonPress) { + tooltip->toggleAnimated(false); + } + return base::EventFilterResult::Continue; + }); + + const auto update = [=] { + const auto geometry = MapFrom(parent, target, target->rect()); + const auto countPosition = [=](QSize size) { + const auto left = geometry.x() + + (geometry.width() - size.width()) / 2; + const auto right = parent->width() + - st::normalFont->spacew; + return QPoint( + std::max(std::min(left, right - size.width()), 0), + geometry.y() - size.height() - st::normalFont->descent); + }; + tooltip->pointAt(geometry, RectPart::Top, countPosition); + }; + parent->widthValue( + ) | rpl::start_with_next(update, tooltip->lifetime()); + + update(); + tooltip->toggleAnimated(true); + + data->raw = tooltip; + tooltip->shownValue() | rpl::filter( + !rpl::mappers::_1 + ) | rpl::start_with_next([=] { + crl::on_main(tooltip, [=] { + if (tooltip->isHidden()) { + if (data->raw == tooltip) { + data->raw = nullptr; + } + delete tooltip; + } + }); + }, tooltip->lifetime()); + + base::timer_once( + duration + ) | rpl::start_with_next([=] { + tooltip->toggleAnimated(false); + }, tooltip->lifetime()); +} + +object_ptr MakeTableValueWithTooltip( + not_null table, + std::shared_ptr data, + TextWithEntities price, + TextWithEntities tooltip, + const Text::MarkedContext &context) { + const auto label = CreateChild( + table, + rpl::single(price), + table->st().defaultValue, + st::defaultPopupMenu, + context); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + + const auto handler = [=](not_null button) { + ShowTableRowTooltip( + data, + button, + rpl::single(tooltip), + kTooltipDuration, + context); + }; + auto text = rpl::single(u"?"_q); + return MakeValueWithSmallButton(table, label, std::move(text), handler); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/table_rows.h b/Telegram/SourceFiles/ui/controls/table_rows.h new file mode 100644 index 0000000000..a5c0c120cc --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/table_rows.h @@ -0,0 +1,83 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace st { +extern const style::margins &giveawayGiftCodeLabelMargin; +extern const style::margins &giveawayGiftCodeValueMargin; +} // namespace st + +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + +namespace Ui { + +class RpWidget; +class TableLayout; +class FlatLabel; +class ImportantTooltip; + +void AddTableRow( + not_null table, + rpl::producer label, + object_ptr value, + style::margins valueMargins = st::giveawayGiftCodeValueMargin); + +not_null AddTableRow( + not_null table, + rpl::producer label, + rpl::producer value, + const Text::MarkedContext &context = {}); + +void AddTableRow( + not_null table, + rpl::producer label, + std::shared_ptr show, + PeerId id); + + +[[nodiscard]] object_ptr MakeValueWithSmallButton( + not_null table, + not_null value, + rpl::producer buttonText, + Fn button)> handler = nullptr, + int topSkip = 0); +[[nodiscard]] object_ptr MakePeerTableValue( + not_null table, + std::shared_ptr show, + PeerId id, + rpl::producer button = nullptr, + Fn handler = nullptr); +[[nodiscard]] object_ptr MakePeerWithStatusValue( + not_null table, + std::shared_ptr show, + PeerId id, + Fn, EmojiStatusId)> pushStatusId); +[[nodiscard]] object_ptr MakeHiddenPeerTableValue( + not_null table); + +struct TableRowTooltipData { + not_null parent; + ImportantTooltip *raw = nullptr; +}; +void ShowTableRowTooltip( + std::shared_ptr data, + not_null target, + rpl::producer text, + int duration, + const Text::MarkedContext &context = {}); + +[[nodiscard]] object_ptr MakeTableValueWithTooltip( + not_null table, + std::shared_ptr data, + TextWithEntities price, + TextWithEntities tooltip, + const Text::MarkedContext &context = {}); + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 6ba52583e3..21e51b60f5 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -89,6 +89,9 @@ starIconEmojiLarge: IconEmoji { icon: icon {{ "settings/premium/star", settingsIconFg }}; padding: margins(0px, -2px, 0px, 0px); } +starIconEmojiInline: IconEmoji(starIconEmojiSmall) { + padding: margins(0px, 3px, 0px, 0px); +} tonIconEmoji: IconEmoji { icon: icon{{ "chat/mini_ton_bold", currencyFg }}; @@ -437,3 +440,7 @@ videoStreamStarsCover: PremiumCover(creditsLowBalancePremiumCover) { minWidth: 190px; } } + +auctionInfoPreviewMargin: margins(0px, 24px, 0px, 8px); +auctionInfoSubtitleSkip: 8px; +auctionInfoTableMargin: margins(0px, 12px, 0px, 12px); diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 31c82c8a3e..9af4555ad2 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 31c82c8a3eee293c72c54515e0c6075e2ee342b2 +Subproject commit 9af4555ad27f72a50e792b41d2053a7943002dcf