/* 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/gift_premium_box.h" #include "api/api_premium.h" #include "api/api_premium_option.h" #include "apiwrap.h" #include "base/event_filter.h" #include "base/timer_rpl.h" #include "base/unixtime.h" #include "base/weak_ptr.h" #include "boxes/peer_list_controllers.h" // ContactsBoxController. #include "boxes/peers/prepare_short_info_box.h" #include "boxes/peers/replace_boost_box.h" // BoostsForGift. #include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox. #include "boxes/star_gift_box.h" // ShowStarGiftBox. #include "boxes/star_gift_preview_box.h" // StarGiftPreviewBox. #include "core/ui_integration.h" #include "data/components/gift_auctions.h" #include "data/data_boosts.h" #include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_credits.h" #include "data/data_document.h" #include "data/data_emoji_statuses.h" #include "data/data_media_types.h" // Data::GiveawayStart. #include "data/data_peer_values.h" // Data::PeerPremiumValue. #include "data/data_session.h" #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/earn/earn_icons.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" #include "mainwidget.h" #include "payments/payments_checkout_process.h" #include "payments/payments_form.h" #include "settings/settings_credits_graphics.h" #include "settings/settings_premium.h" #include "ui/basic_click_handlers.h" // UrlClickHandler::Open. #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" #include "ui/effects/premium_top_bar.h" #include "ui/effects/spoiler_mess.h" #include "ui/layers/generic_box.h" #include "ui/painter.h" #include "ui/rect.h" #include "ui/ui_utility.h" #include "ui/vertical_list.h" #include "ui/text/custom_emoji_helper.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/gradient_round_button.h" #include "ui/widgets/tooltip.h" #include "ui/wrap/padding_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/table_layout.h" #include "window/window_peer_menu.h" // ShowChooseRecipientBox. #include "window/window_session_controller.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" #include "styles/style_premium.h" #include namespace { constexpr auto kTooltipDuration = 3 * crl::time(1000); constexpr auto kHorizontalBar = QChar(0x2015); constexpr auto kSpinnerRows = 6; constexpr auto kSpinDuration = crl::time(120); using Ui::AddTableRow; using Ui::TableRowTooltipData; using SpinnerState = Data::GiftUpgradeSpinner::State; [[nodiscard]] QString CreateMessageLink( not_null session, PeerId peerId, uint64 messageId) { if (const auto msgId = MsgId(peerId ? messageId : 0)) { const auto peer = session->data().peer(peerId); if (const auto channel = peer->asBroadcast()) { const auto username = channel->username(); const auto base = username.isEmpty() ? u"c/%1"_q.arg(peerToChannel(channel->id).bare) : username; const auto query = base + '/' + QString::number(msgId.bare); return session->createInternalLink(query); } } return QString(); }; [[nodiscard]] QString FixupTransactionId(QString origin) { return origin.replace(kHorizontalBar, QChar('-')); } [[nodiscard]] Data::GiftCodeLink MakeGiftCodeLink( not_null session, const QString &slug) { const auto path = u"giftcode/"_q + slug; return { session->createInternalLink(path), session->createInternalLinkFull(path), }; } [[nodiscard]] TextWithEntities FormatValuePrice( int64 price, QString currency, bool approximately = false) { auto result = TextWithEntities(); if (approximately) { result.append('~'); } return result.append(Ui::FillAmountAndCurrency(price, currency)); } [[nodiscard]] TextWithEntities FormatValueDate(TimeId date) { const auto parsed = base::unixtime::parse(date).date(); const auto day = parsed.day(); const auto month = parsed.month(); const auto year = parsed.year(); return { tr::lng_month_day_year( tr::now, lt_month, Lang::MonthDay(month)(tr::now), lt_day, QString::number(day), lt_year, QString::number(year)) }; } [[nodiscard]] object_ptr MakeLinkCopyIcon( not_null parent) { auto result = object_ptr(parent); const auto raw = result.data(); raw->paintRequest() | rpl::on_next([=] { auto p = QPainter(raw); const auto &icon = st::giveawayGiftCodeLinkCopy; const auto left = (raw->width() - icon.width()) / 2; const auto top = (raw->height() - icon.height()) / 2; icon.paint(p, left, top, raw->width()); }, raw->lifetime()); raw->resize( st::giveawayGiftCodeLinkCopyWidth, st::giveawayGiftCodeLinkHeight); raw->setAttribute(Qt::WA_TransparentForMouseEvents); return result; } [[nodiscard]] tr::phrase GiftDurationPhrase(int days) { return (days < 30) ? tr::lng_premium_gift_duration_days : (days < 30 * 12) ? tr::lng_premium_gift_duration_months : tr::lng_premium_gift_duration_years; } [[nodiscard]] object_ptr MakeMaybeMultilineTokenValue( not_null table, QString token, Settings::CreditsEntryBoxStyleOverrides st) { constexpr auto kOneLineCount = 24; token = token.replace(QChar('-'), kHorizontalBar); const auto oneLine = token.length() <= kOneLineCount; return object_ptr( table, rpl::single( Ui::Text::Wrapped({ token }, EntityType::Code, {})), (oneLine ? table->st().defaultValue : st.tableValueMultiline ? *st.tableValueMultiline : st::giveawayGiftCodeValueMultiline)); } [[nodiscard]] object_ptr MakePriceWithChangePercentValue( not_null table, const std::shared_ptr &value) { auto label = object_ptr( table, rpl::single(FormatValuePrice(value->lastSalePrice, value->currency)), table->st().defaultValue); label->setAttribute(Qt::WA_TransparentForMouseEvents); const auto initial = value->initialSalePrice; if (!initial) { return label; } const auto diff = (100 * (value->lastSalePrice - initial)) / float64(initial); const auto use = (std::abs(diff) >= 10.) ? base::SafeRound(diff) : (int(base::SafeRound(diff * 100)) / 100.); const auto prefix = (use > 0) ? u"+"_q : QString(); const auto percent = Lang::FormatExactCountDecimal(use) + '%'; auto text = rpl::single(prefix + percent); return MakeValueWithSmallButton(table, label, std::move(text)); } [[nodiscard]] object_ptr MakeMinimumPriceValue( not_null table, std::shared_ptr tooltip, const std::shared_ptr &unique) { const auto &value = unique->value; const auto text = FormatValuePrice(value->minimumPrice, value->currency); return Ui::MakeTableValueWithTooltip( table, std::move(tooltip), text, tr::lng_gift_value_minimum_price_tooltip( tr::now, lt_amount, tr::bold(text.text), lt_gift, tr::bold(unique->title), tr::marked)); } [[nodiscard]] object_ptr MakeAveragePriceValue( not_null table, std::shared_ptr tooltip, const std::shared_ptr &unique) { const auto &value = unique->value; const auto text = FormatValuePrice(value->averagePrice, value->currency); return Ui::MakeTableValueWithTooltip( table, std::move(tooltip), text, tr::lng_gift_value_average_price_tooltip( tr::now, lt_amount, tr::bold(text.text), lt_gift, tr::bold(unique->title), tr::marked)); } [[nodiscard]] object_ptr MakeAttributeValue( not_null table, const Data::UniqueGiftAttribute &attribute, Fn, int)> showTooltip) { const auto label = Ui::CreateChild( table, attribute.name, table->st().defaultValue); label->setAttribute(Qt::WA_TransparentForMouseEvents); const auto permille = attribute.rarityPermille; auto text = rpl::single(QString::number(permille / 10.) + '%'); const auto handler = [=](not_null button) { showTooltip(button, permille); }; return MakeValueWithSmallButton(table, label, std::move(text), handler); } [[nodiscard]] object_ptr MakeAttributeValue( not_null table, const Data::UniqueGiftAttribute &attribute, Fn, int)> showTooltip, std::shared_ptr spinner, std::vector spinning, SpinnerState finishedState) { if (!spinner) { return MakeAttributeValue(table, attribute, showTooltip); } auto result = object_ptr(table); const auto raw = result.get(); struct Row { Data::UniqueGiftAttribute attribute; object_ptr widget; QImage frame; }; struct State { int wasIndex = 0; int nowIndex = 0; std::vector rows; Ui::Animations::Simple animation; QImage fading; Fn repaint; bool finished = false; }; const auto state = raw->lifetime().make_state(); state->repaint = [=] { raw->update(); }; const auto margin = st::giveawayGiftCodeValueMargin; const auto startWithin = [=] { Expects(!state->rows.empty()); state->wasIndex = state->nowIndex; state->nowIndex = (state->nowIndex + 1) % state->rows.size(); state->animation.start(state->repaint, 0., 1., kSpinDuration); }; const auto startToTarget = [=] { if (state->nowIndex != 0) { state->wasIndex = state->nowIndex; state->nowIndex = 0; state->animation.start( state->repaint, 0., 1., kSpinDuration * 3, anim::easeOutCubic); } }; const auto add = [&](const Data::UniqueGiftAttribute &value) { if (state->rows.size() >= kSpinnerRows) { return false; } const auto already = ranges::contains( state->rows, value, &Row::attribute); if (!already) { state->rows.push_back(Row{ .attribute = value, .widget = MakeAttributeValue(table, value, showTooltip), }); const auto widget = state->rows.back().widget.get(); widget->setParent(raw); widget->hide(); } return true; }; add(attribute); for (const auto &item : spinning) { if (!add(item)) { break; } } raw->widthValue() | rpl::on_next([=](int width) { auto height = 0; const auto inner = width - margin.left() - margin.right(); if (inner > 0) { for (const auto &row : state->rows) { row.widget->resizeToWidth(inner); row.widget->move(margin.left(), margin.top()); height = std::max(height, row.widget->height()); } } raw->resize(width, margin.top() + height + margin.bottom()); crl::on_main(raw, [=] { for (const auto &row : state->rows) { Ui::SendPendingMoveResizeEvents(row.widget.get()); } }); }, raw->lifetime()); style::PaletteChanged() | rpl::on_next([=] { for (auto &row : state->rows) { row.frame = QImage(); } state->fading = QImage(); }, raw->lifetime()); raw->paintOn([=](QPainter &p) { if (state->finished) { return; } auto progress = state->animation.value(1.); if (progress >= 1.) { const auto ending = (spinner->state.current() >= finishedState); if (ending) { startToTarget(); } else { startWithin(); } progress = state->animation.value(1.); if (ending && progress >= 1.) { state->rows.front().widget->show(); state->finished = true; return; } } const auto ratio = style::DevicePixelRatio(); const auto h = raw->height(); if (state->fading.height() != h * ratio) { state->fading = QImage( QSize(1, h) * ratio, QImage::Format_ARGB32_Premultiplied); state->fading.setDevicePixelRatio(ratio); state->fading.fill(Qt::transparent); auto q = QPainter(&state->fading); auto brush = QLinearGradient(0, 0, 0, margin.top()); brush.setStops({ { 0., anim::with_alpha(st::boxBg->c, 1.) }, { 1., anim::with_alpha(st::boxBg->c, 0.) }, }); q.fillRect(0, 0, 1, margin.top(), brush); brush.setStart(0, h); brush.setFinalStop(0, h - margin.bottom()); q.fillRect(0, h - margin.bottom(), 1, margin.bottom(), brush); } auto &was = state->rows[state->wasIndex]; auto &now = state->rows[state->nowIndex]; const auto validate = [&](Row &row) { const auto size = row.widget->size(); if (row.frame.size() != size * ratio) { row.frame = Ui::GrabWidgetToImage(row.widget.get()); } }; validate(was); validate(now); const auto t = progress * (margin.top() + was.widget->height()); const auto b = progress * (now.widget->height() + margin.bottom()); p.drawImage( margin.left(), margin.top() - int(base::SafeRound(t)), was.frame); p.drawImage( margin.left(), raw->height() - int(base::SafeRound(b)), now.frame); p.drawImage(raw->rect(), state->fading); }); startWithin(); return result; } void AddUniqueGiftPropertyRows( not_null container, std::shared_ptr show, not_null table, std::shared_ptr unique, std::shared_ptr spinner) { const auto tooltip = std::make_shared( TableRowTooltipData{ .parent = container }); const auto showTooltip = [=]( not_null widget, rpl::producer text) { ShowTableRowTooltip( tooltip, widget, std::move(text), kTooltipDuration); }; struct VariantsList { rpl::variable attributes; bool requested = false; bool inited = false; rpl::lifetime clickLifetime; }; const auto variants = container->lifetime().make_state(); const auto session = &unique->model.document->session(); const auto giftId = unique->initialGiftId; const auto initVariants = [=] { if (variants->requested || variants->inited) { return; } const auto auctions = &session->giftAuctions(); if (auto attributes = auctions->attributes(giftId)) { variants->inited = true; variants->attributes = std::move(*attributes); } else { variants->requested = true; auctions->requestAttributes(giftId, crl::guard(container, [=] { variants->inited = true; variants->attributes.force_assign( *auctions->attributes(giftId)); })); } }; const auto title = unique->title; const auto showRarity = [=](Data::GiftAttributeId id) { return [=]( not_null widget, int rarity) { initVariants(); const auto weak = base::make_weak(widget); variants->clickLifetime = variants->attributes.value( ) | rpl::filter([=] { return variants->inited; }) | rpl::take(1) | rpl::on_next([=]( const Data::UniqueGiftAttributes &list) { if (!list.models.empty()) { show->show(Box( Ui::StarGiftPreviewBox, title, list, id.type, unique)); } else if (const auto widget = weak.get()) { const auto percent = QString::number(rarity / 10.) + '%'; showTooltip(widget, tr::lng_gift_unique_rarity( lt_percent, rpl::single(TextWithEntities{ percent }), tr::marked)); } }); }; }; const auto empty = std::vector(); const auto extract = [&](const auto &list) { auto result = empty; result.reserve(list.size()); for (const auto &item : list) { result.push_back(item); } return result; }; const auto attributes = spinner ? &spinner->attributes : nullptr; const auto models = spinner ? extract(attributes->models) : empty; const auto patterns = spinner ? extract(attributes->patterns) : empty; const auto backdrops = spinner ? extract(attributes->backdrops) : empty; const auto margin = spinner ? style::margins() : st::giveawayGiftCodeValueMargin; AddTableRow( table, tr::lng_gift_unique_model(), MakeAttributeValue( table, unique->model, showRarity(IdFor(unique->model)), spinner, models, SpinnerState::FinishedModel), margin); AddTableRow( table, tr::lng_gift_unique_symbol(), MakeAttributeValue( table, unique->pattern, showRarity(IdFor(unique->pattern)), spinner, patterns, SpinnerState::FinishedPattern), margin); AddTableRow( table, tr::lng_gift_unique_backdrop(), MakeAttributeValue( table, unique->backdrop, showRarity(IdFor(unique->backdrop)), spinner, backdrops, SpinnerState::FinishedBackdrop), margin); } [[nodiscard]] object_ptr MakeStarGiftStarsValue( not_null table, std::shared_ptr show, const Data::CreditsHistoryEntry &entry, Fn convertToStars) { auto helper = Ui::Text::CustomEmojiHelper(); const auto addUpgradeToValue = !entry.credits.ton() && !entry.giftUpgradeGifted && !entry.giftUpgradeSeparate && entry.starsUpgradedBySender; const auto amount = addUpgradeToValue ? CreditsAmount( entry.credits.whole() + entry.starsUpgradedBySender, entry.credits.nano()) : entry.credits; const auto price = helper.paletteDependent(Ui::Earn::IconCreditsEmoji( )).append(' ').append(Lang::FormatCreditsAmountDecimal(amount)); auto label = object_ptr( table, rpl::single(price), table->st().defaultValue, st::defaultPopupMenu, helper.context()); label->setAttribute(Qt::WA_TransparentForMouseEvents); if (!convertToStars) { return label; } const auto handler = [=](not_null button) { convertToStars(); }; auto text = tr::lng_gift_sell_small( lt_count_decimal, rpl::single(entry.starsConverted * 1.)); return MakeValueWithSmallButton( table, label.release(), std::move(text), handler); } [[nodiscard]] object_ptr MakeUniqueGiftValueValue( not_null table, std::shared_ptr show, const Data::CreditsHistoryEntry &entry, Settings::CreditsEntryBoxStyleOverrides st) { const auto unique = entry.uniqueGift; const auto value = unique ? unique->value : nullptr; const auto loading = std::make_shared(false); const auto label = Ui::CreateChild( table, rpl::single( FormatValuePrice(value->valuePrice, value->currency, true)), table->st().defaultValue, st::defaultPopupMenu); label->setAttribute(Qt::WA_TransparentForMouseEvents); const auto handler = [=](not_null button) { if (value->initialPriceStars) { show->show(Box(Settings::UniqueGiftValueBox, show, entry, st)); return; } else if (*loading) { return; } *loading = true; show->session().api().request(MTPpayments_GetUniqueStarGiftValueInfo( MTP_string(unique->slug) )).done([=](const MTPpayments_UniqueStarGiftValueInfo &result) { *loading = false; const auto &data = result.data(); value->currency = qs(data.vcurrency()); value->valuePrice = data.vvalue().v; value->initialSaleDate = data.vinitial_sale_date().v; value->initialPriceStars = CreditsAmount( data.vinitial_sale_stars().v); value->initialSalePrice = data.vinitial_sale_price().v; value->lastSaleDate = data.vlast_sale_date().value_or_empty(); value->lastSalePrice = data.vlast_sale_price().value_or_empty(); value->lastSaleFragment = data.is_last_sale_on_fragment(); value->minimumPrice = data.vfloor_price().value_or_empty(); value->averagePrice = data.vaverage_price().value_or_empty(); value->forSaleOnTelegram = data.vlisted_count().value_or_empty(); value->forSaleOnFragment = int( data.vfragment_listed_count().value_or_empty()); value->fragmentUrl = qs( data.vfragment_listed_url().value_or_empty()); show->show(Box(Settings::UniqueGiftValueBox, show, entry, st)); }).send(); }; return MakeValueWithSmallButton( table, label, tr::lng_gift_unique_value_learn_more(), handler); } void AddTable( not_null container, std::shared_ptr show, Settings::CreditsEntryBoxStyleOverrides st, const Api::GiftCode ¤t, bool skipReason) { auto table = container->add( object_ptr( container, st.table ? *st.table : st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); if (current.from) { AddTableRow( table, tr::lng_gift_link_label_from(), show, current.from); } if (current.from && current.to) { AddTableRow( table, tr::lng_gift_link_label_to(), show, current.to); } else if (current.from) { AddTableRow( table, tr::lng_gift_link_label_to(), tr::lng_gift_link_label_to_unclaimed(tr::marked)); } AddTableRow( table, tr::lng_gift_link_label_gift(), tr::lng_gift_link_gift_premium( lt_duration, GiftDurationValue(current.days) | rpl::map(tr::marked), tr::marked)); if (!skipReason && current.from) { const auto reason = AddTableRow( table, tr::lng_gift_link_label_reason(), (current.giveawayId ? ((current.to ? tr::lng_gift_link_reason_giveaway : tr::lng_gift_link_reason_unclaimed)(tr::link)) : current.giveaway ? ((current.to ? tr::lng_gift_link_reason_giveaway : tr::lng_gift_link_reason_unclaimed)( tr::marked ) | rpl::type_erased) : tr::lng_gift_link_reason_chosen(tr::marked))); reason->setClickHandlerFilter([=](const auto &...) { if (const auto window = show->resolveWindow()) { window->showPeerHistory( current.from, Window::SectionShow::Way::Forward, current.giveawayId); } return false; }); } if (current.date) { AddTableRow( table, tr::lng_gift_link_label_date(), rpl::single(tr::marked( langDateTime(base::unixtime::parse(current.date))))); } } void ShareWithFriend( not_null navigation, const QString &slug) { const auto chosen = [=](not_null thread) { const auto content = navigation->parentController()->content(); return content->shareUrl( thread, MakeGiftCodeLink(&navigation->session(), slug).link, QString()); }; Window::ShowChooseRecipientBox(navigation, chosen); } void ShowAlreadyPremiumToast( not_null navigation, const QString &slug, TimeId date) { const auto instance = std::make_shared< base::weak_ptr >(); const auto shareLink = [=]( const ClickHandlerPtr &, Qt::MouseButton button) { if (button == Qt::LeftButton) { if (const auto strong = instance->get()) { strong->hideAnimated(); } ShareWithFriend(navigation, slug); } return false; }; *instance = navigation->showToast({ .title = tr::lng_gift_link_already_title(tr::now), .text = tr::lng_gift_link_already_about( tr::now, lt_date, tr::bold(langDateTime(base::unixtime::parse(date))), lt_link, tr::link( tr::bold(tr::lng_gift_link_already_link(tr::now))), tr::marked), .filter = crl::guard(navigation, shareLink), .duration = 6 * crl::time(1000), }); } } // namespace rpl::producer GiftDurationValue(int days) { return GiftDurationPhrase(days)( lt_count, rpl::single(float64((days < 30) ? days : (days < 30 * 12) ? (days / 30) : (days / (30 * 12))))); } QString GiftDuration(int days) { return GiftDurationPhrase(days)(tr::now, lt_count, (days < 30) ? days : (days < 30 * 12) ? (days / 30) : (days / (30 * 12))); } void GiftCodeBox( not_null box, not_null controller, const QString &slug) { struct State { rpl::variable data; rpl::variable used; bool sent = false; }; const auto session = &controller->session(); const auto state = box->lifetime().make_state(State{}); state->data = session->api().premium().giftCodeValue(slug); state->used = state->data.value( ) | rpl::map([=](const Api::GiftCode &data) { return data.used != 0; }); box->setWidth(st::boxWideWidth); box->setStyle(st::giveawayGiftCodeBox); box->setNoContentMargin(true); const auto bar = box->setPinnedToTopContent( object_ptr( box, st::giveawayGiftCodeCover, Ui::Premium::TopBarDescriptor{ .clickContextOther = nullptr, .title = rpl::conditional( state->used.value(), tr::lng_gift_link_used_title(), tr::lng_gift_link_title()), .about = rpl::conditional( state->used.value(), tr::lng_gift_link_used_about(tr::rich), tr::lng_gift_link_about(tr::rich)), .light = true, })); const auto max = st::giveawayGiftCodeTopHeight; bar->setMaximumHeight(max); bar->setMinimumHeight(st::infoLayerTopBarHeight); bar->resize(bar->width(), bar->maximumHeight()); const auto link = MakeGiftCodeLink(&controller->session(), slug); box->addRow( Ui::MakeLinkLabel( box, rpl::single(link.text), rpl::single(link.link), box->uiShow(), MakeLinkCopyIcon(box)), st::giveawayGiftCodeLinkMargin); const auto show = controller->uiShow(); AddTable(box->verticalLayout(), show, {}, state->data.current(), false); auto shareLink = tr::lng_gift_link_also_send_link( ) | rpl::map([](const QString &text) { return tr::link(text); }); auto richDate = [](const Api::GiftCode &data) { return TextWithEntities{ langDateTime(base::unixtime::parse(data.used)), }; }; const auto footer = box->addRow( object_ptr( box, rpl::conditional( state->used.value(), tr::lng_gift_link_used_footer( lt_date, state->data.value() | rpl::map(richDate), tr::marked), tr::lng_gift_link_also_send( lt_link, std::move(shareLink), tr::marked)), st::giveawayGiftCodeFooter), st::giveawayGiftCodeFooterMargin, style::al_top); footer->setClickHandlerFilter([=](const auto &...) { ShareWithFriend(controller, slug); return false; }); const auto close = Ui::CreateChild( box.get(), st::boxTitleClose); close->setClickedCallback([=] { box->closeBox(); }); box->widthValue( ) | rpl::on_next([=](int width) { close->moveToRight(0, 0); }, box->lifetime()); box->addButton(rpl::conditional( state->used.value(), tr::lng_box_ok(), tr::lng_gift_link_use() ), [=] { if (state->used.current()) { box->closeBox(); } else if (!state->sent) { state->sent = true; const auto done = crl::guard(box, [=](const QString &error) { const auto activePrefix = u"PREMIUM_SUB_ACTIVE_UNTIL_"_q; if (error.isEmpty()) { auto copy = state->data.current(); copy.used = base::unixtime::now(); state->data = std::move(copy); Ui::StartFireworks(box->parentWidget()); } else if (error.startsWith(activePrefix)) { const auto date = error.mid(activePrefix.size()).toInt(); ShowAlreadyPremiumToast(controller, slug, date); state->sent = false; } else { box->uiShow()->showToast(error); state->sent = false; } }); controller->session().api().premium().applyGiftCode(slug, done); } }); } void GiftCodePendingBox( not_null box, not_null controller, const Api::GiftCode &data) { box->setWidth(st::boxWideWidth); box->setStyle(st::giveawayGiftCodeBox); box->setNoContentMargin(true); { const auto peerTo = controller->session().data().peer(data.to); const auto clickContext = [=, weak = base::make_weak(controller)] { if (const auto strong = weak.get()) { strong->uiShow()->showBox( PrepareShortInfoBox(peerTo, strong)); } return QVariant(); }; const auto &st = st::giveawayGiftCodeCover; const auto resultToName = st.about.style.font->elided( peerTo->shortName(), st.about.minWidth / 2, Qt::ElideMiddle); const auto bar = box->setPinnedToTopContent( object_ptr( box, st, Ui::Premium::TopBarDescriptor{ .clickContextOther = clickContext, .title = tr::lng_gift_link_title(), .about = tr::lng_gift_link_pending_about( lt_user, rpl::single(tr::link(resultToName)), tr::rich), .light = true, })); const auto max = st::giveawayGiftCodeTopHeight; bar->setMaximumHeight(max); bar->setMinimumHeight(st::infoLayerTopBarHeight); bar->resize(bar->width(), bar->maximumHeight()); } { const auto linkLabel = box->addRow( Ui::MakeLinkLabel(box, nullptr, nullptr, nullptr, nullptr), st::giveawayGiftCodeLinkMargin); const auto spoiler = Ui::CreateChild(linkLabel); spoiler->lifetime().make_state([=] { spoiler->update(); })->start(); linkLabel->sizeValue( ) | rpl::on_next([=](const QSize &s) { spoiler->setGeometry(Rect(s)); }, spoiler->lifetime()); const auto spoilerCached = Ui::SpoilerMessCached( Ui::DefaultTextSpoilerMask(), st::giveawayGiftCodeLink.textFg->c); const auto textHeight = st::giveawayGiftCodeLink.style.font->height; spoiler->paintRequest( ) | rpl::on_next([=] { auto p = QPainter(spoiler); const auto rect = spoiler->rect(); const auto r = rect - QMargins( st::boxRowPadding.left(), (rect.height() - textHeight) / 2, st::boxRowPadding.right(), (rect.height() - textHeight) / 2); Ui::FillSpoilerRect(p, r, spoilerCached.frame()); }, spoiler->lifetime()); spoiler->setClickedCallback([show = box->uiShow()] { show->showToast(tr::lng_gift_link_pending_toast(tr::now)); }); spoiler->show(); } const auto show = controller->uiShow(); AddTable(box->verticalLayout(), show, {}, data, true); box->addRow( object_ptr( box, tr::lng_gift_link_pending_footer(), st::giveawayGiftCodeFooter), st::giveawayGiftCodeFooterMargin, style::al_top); const auto close = Ui::CreateChild( box.get(), st::boxTitleClose); const auto closeCallback = [=] { box->closeBox(); }; close->setClickedCallback(closeCallback); box->widthValue( ) | rpl::on_next([=](int width) { close->moveToRight(0, 0); }, box->lifetime()); box->addButton(tr::lng_close(), closeCallback); } void ResolveGiftCode( not_null controller, const QString &slug, PeerId fromId, PeerId toId) { const auto done = [=](Api::GiftCode code) { const auto session = &controller->session(); const auto selfId = session->userPeerId(); if (!code) { controller->showToast(tr::lng_gift_link_expired(tr::now)); } else if (!code.from && fromId == selfId) { code.from = fromId; code.to = toId; const auto self = (fromId == selfId); const auto peer = session->data().peer(self ? toId : fromId); const auto days = code.days; const auto parent = controller->parentController(); Settings::ShowGiftPremium(parent, peer, days, self); } else { controller->uiShow()->showBox(Box(GiftCodeBox, controller, slug)); } }; controller->session().api().premium().checkGiftCode( slug, crl::guard(controller, done)); } void GiveawayInfoBox( not_null box, not_null controller, std::optional start, std::optional results, Api::GiveawayInfo info) { Expects(start || results); using State = Api::GiveawayState; const auto finished = (info.state == State::Finished) || (info.state == State::Refunded); box->setTitle((finished ? tr::lng_prizes_end_title : tr::lng_prizes_how_title)()); const auto first = results ? results->channel->name() : !start->channels.empty() ? start->channels.front()->name() : u"channel"_q; auto resultText = (!info.giftCode.isEmpty()) ? tr::lng_prizes_you_won( lt_cup, rpl::single( TextWithEntities{ QString::fromUtf8("\xf0\x9f\x8f\x86") }), tr::marked) : (info.credits) ? tr::lng_prizes_you_won_credits( lt_amount, tr::lng_prizes_you_won_credits_amount( lt_count, rpl::single(float64(info.credits)), tr::bold), lt_cup, rpl::single( TextWithEntities{ QString::fromUtf8("\xf0\x9f\x8f\x86") }), tr::marked) : (info.state == State::Finished) ? tr::lng_prizes_you_didnt(tr::marked) : (rpl::producer)(nullptr); if (resultText) { const auto &st = st::changePhoneDescription; const auto skip = st.style.font->height * 0.5; auto label = object_ptr( box.get(), std::move(resultText), st); if ((!info.giftCode.isEmpty()) || info.credits) { label->setTextColorOverride(st::windowActiveTextFg->c); } const auto result = box->addRow( object_ptr>( box.get(), std::move(label), QMargins(0, skip, 0, skip)), style::al_justify); result->paintRequest() | rpl::on_next([=] { auto p = QPainter(result); p.setPen(Qt::NoPen); p.setBrush(st::boxDividerBg); p.drawRoundedRect(result->rect(), st::boxRadius, st::boxRadius); }, result->lifetime()); Ui::AddSkip(box->verticalLayout()); } auto text = TextWithEntities(); const auto quantity = start ? start->quantity : (results->winnersCount + results->unclaimedCount); const auto months = start ? start->months : results->months; const auto group = results ? results->channel->isMegagroup() : (!start->channels.empty() && start->channels.front()->isMegagroup()); const auto credits = start ? start->credits : (results ? results->credits : 0); text.append((finished ? tr::lng_prizes_end_text : tr::lng_prizes_how_text)( tr::now, lt_admins, credits ? (group ? tr::lng_prizes_credits_admins_group : tr::lng_prizes_credits_admins)( tr::now, lt_channel, tr::bold(first), lt_amount, tr::lng_prizes_credits_admins_amount( tr::now, lt_count_decimal, float64(credits), tr::bold), tr::rich) : (group ? tr::lng_prizes_admins_group : tr::lng_prizes_admins)( tr::now, lt_count, quantity, lt_channel, tr::bold(first), lt_duration, TextWithEntities{ GiftDuration(months * 30) }, tr::rich), tr::rich)); const auto many = start ? (start->channels.size() > 1) : (results->additionalPeersCount > 0); const auto count = info.winnersCount ? info.winnersCount : quantity; const auto all = start ? start->all : results->all; auto winners = all ? (many ? (group ? tr::lng_prizes_winners_all_of_many_group : tr::lng_prizes_winners_all_of_many) : (group ? tr::lng_prizes_winners_all_of_one_group : tr::lng_prizes_winners_all_of_one))( tr::now, lt_count, count, lt_channel, tr::bold(first), tr::rich) : (many ? tr::lng_prizes_winners_new_of_many : tr::lng_prizes_winners_new_of_one)( tr::now, lt_count, count, lt_channel, tr::bold(first), lt_start_date, tr::bold( langDateTime(base::unixtime::parse(info.startDate))), tr::rich); const auto additionalPrize = results ? results->additionalPrize : start->additionalPrize; if (!additionalPrize.isEmpty()) { text.append("\n\n").append((group ? tr::lng_prizes_additional_added_group : tr::lng_prizes_additional_added)( tr::now, lt_count, count, lt_channel, tr::bold(first), lt_prize, TextWithEntities{ additionalPrize }, tr::rich)); } const auto untilDate = start ? start->untilDate : results->untilDate; text.append("\n\n").append((finished ? tr::lng_prizes_end_when_finish : tr::lng_prizes_how_when_finish)( tr::now, lt_date, tr::bold(langDayOfMonthFull( base::unixtime::parse(untilDate).date())), lt_winners, winners, tr::rich)); if (info.activatedCount > 0) { text.append(' ').append(tr::lng_prizes_end_activated( tr::now, lt_count, info.activatedCount, tr::rich)); } if (!info.giftCode.isEmpty() || info.state == State::Finished || info.state == State::Preparing) { } else if (info.state != State::Refunded) { if (info.adminChannelId) { const auto channel = controller->session().data().channel( info.adminChannelId); text.append("\n\n").append((channel->isMegagroup() ? tr::lng_prizes_how_no_admin_group : tr::lng_prizes_how_no_admin)( tr::now, lt_channel, tr::bold(channel->name()), tr::rich)); } else if (info.tooEarlyDate) { const auto channel = controller->session().data().channel( info.adminChannelId); text.append("\n\n").append((channel->isMegagroup() ? tr::lng_prizes_how_no_joined_group : tr::lng_prizes_how_no_joined)( tr::now, lt_date, tr::bold( langDateTime( base::unixtime::parse(info.tooEarlyDate))), tr::rich)); } else if (!info.disallowedCountry.isEmpty()) { text.append("\n\n").append(tr::lng_prizes_how_no_country( tr::now, tr::rich)); } else if (info.participating) { text.append("\n\n").append((many ? tr::lng_prizes_how_yes_joined_many : tr::lng_prizes_how_yes_joined_one)( tr::now, lt_channel, tr::bold(first), tr::rich)); } else { text.append("\n\n").append((many ? tr::lng_prizes_how_participate_many : tr::lng_prizes_how_participate_one)( tr::now, lt_channel, tr::bold(first), lt_date, tr::bold(langDayOfMonthFull( base::unixtime::parse(untilDate).date())), tr::rich)); } } const auto padding = st::boxPadding; box->addRow( object_ptr( box.get(), rpl::single(std::move(text)), st::boxLabel), { padding.left(), 0, padding.right(), padding.bottom() }); if (info.state == State::Refunded) { const auto wrap = box->addRow( object_ptr>( box.get(), object_ptr( box.get(), (group ? tr::lng_prizes_cancelled_group() : tr::lng_prizes_cancelled()), st::giveawayRefundedLabel), st::giveawayRefundedPadding), { padding.left(), 0, padding.right(), padding.bottom() }, style::al_top); const auto bg = wrap->lifetime().make_state( st::boxRadius, st::attentionBoxButton.textBgOver); wrap->paintRequest() | rpl::on_next([=] { auto p = QPainter(wrap); bg->paint(p, wrap->rect()); }, wrap->lifetime()); } if (const auto slug = info.giftCode; !slug.isEmpty()) { box->addButton(tr::lng_prizes_view_prize(), [=] { ResolveGiftCode(controller, slug); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } else { box->addButton(tr::lng_close(), [=] { box->closeBox(); }); } } void ResolveGiveawayInfo( not_null controller, not_null peer, MsgId messageId, std::optional start, std::optional results) { const auto show = [=](Api::GiveawayInfo info) { if (!info) { controller->showToast( tr::lng_confirm_phone_link_invalid(tr::now)); } else { controller->uiShow()->showBox( Box(GiveawayInfoBox, controller, start, results, info)); } }; controller->session().api().premium().resolveGiveawayInfo( peer, messageId, crl::guard(controller, show)); } QString TonAddressUrl( not_null session, const QString &address) { const auto prefix = session->appConfig().get( u"ton_blockchain_explorer_url"_q, u"https://tonviewer.com/"_q); return prefix + address; } struct AddedUniqueDetails { object_ptr widget; not_null label; }; [[nodiscard]] AddedUniqueDetails MakeUniqueDetails( std::shared_ptr show, not_null parent, rpl::producer text, Settings::CreditsEntryBoxStyleOverrides st, Ui::Text::MarkedContext context, int removeCost, Fn removed)> remove) { auto owned = object_ptr( parent, rpl::duplicate(text), (st.tableValueMessage ? *st.tableValueMessage : st::giveawayGiftMessage), st::defaultPopupMenu, context); const auto label = owned.data(); if (!remove) { return { .widget = std::move(owned), .label = label, }; } owned.release(); auto result = object_ptr(parent); const auto raw = result.data(); label->setParent(raw); label->show(); const auto icon = Ui::CreateChild( raw, st::giveawayGiftMessageRemove); raw->widthValue() | rpl::on_next([=](int outer) { label->resizeToWidth(outer - icon->width()); const auto height = std::max(label->height(), icon->height()); icon->moveToRight(0, (height - icon->height()) / 2); label->move(0, (height - label->height()) / 2); raw->resize(outer, height); }, raw->lifetime()); icon->setClickedCallback([=] { const auto weak = base::make_weak(raw); show->show(Box([=](not_null box) { const auto confirming = base::make_weak(box); const auto confirmed = [=] { remove([=] { delete weak.get(); if (const auto strong = confirming.get()) { strong->closeBox(); } }); }; Ui::ConfirmBox(box, { .text = tr::lng_gift_unique_info_remove_text(), .confirmed = confirmed, .confirmText = tr::lng_gift_unique_info_remove_confirm( lt_cost, rpl::single( Ui::Text::IconEmoji(&st::starIconEmoji).append( Lang::FormatCountDecimal(removeCost))), tr::rich), .title = tr::lng_gift_unique_info_remove_title(), }); box->addRow( object_ptr( box, st.table ? *st.table : st::giveawayGiftCodeTable) )->addRow( object_ptr( parent, rpl::duplicate(text), (st.tableValueMessage ? *st.tableValueMessage : st::giveawayGiftMessage), st::defaultPopupMenu, context), nullptr, st::giveawayGiftCodeLabelMargin, st::giveawayGiftCodeValueMargin); })); }); return { .widget = std::move(result), .label = label, }; } void AddStarGiftTable( std::shared_ptr show, not_null container, Settings::CreditsEntryBoxStyleOverrides st, const Data::CreditsHistoryEntry &entry, std::shared_ptr spinner, Fn convertToStars, bool canStartUpgrade, Fn removed)> removeDetails) { const auto table = container->add( object_ptr( container, st.table ? *st.table : st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); const auto peerId = PeerId(entry.barePeerId); const auto session = &show->session(); const auto unique = entry.uniqueGift.get(); const auto selfBareId = session->userPeerId().value; const auto giftToSelf = (peerId == session->userPeerId()) && (entry.in || entry.bareGiftOwnerId == selfBareId); const auto giftToChannel = entry.giftChannelSavedId && peerIsChannel(PeerId(entry.bareEntryOwnerId)); const auto tooltip = std::make_shared( TableRowTooltipData{ .parent = container }); const auto showTooltip = [=]( not_null widget, rpl::producer text) { ShowTableRowTooltip(tooltip, widget, std::move(text), kTooltipDuration); }; if (unique && entry.bareGiftResaleRecipientId) { AddTableRow( table, tr::lng_credits_box_history_entry_peer(), MakePeerTableValue( table, show, PeerId(entry.bareGiftResaleRecipientId)), st::giveawayGiftCodePeerMargin); } else if (unique && entry.bareGiftOwnerId) { const auto ownerId = PeerId(entry.bareGiftOwnerId); const auto was = std::make_shared>(); const auto handleChange = [=]( not_null badge, EmojiStatusId emojiStatusId) { const auto id = emojiStatusId.collectible ? emojiStatusId.collectible->id : 0; const auto show = [&](const auto &phrase) { showTooltip(badge, phrase( lt_name, rpl::single(tr::bold(UniqueGiftName(*unique))), tr::marked)); }; if (!*was || *was == id) { *was = id; return; } else if (*was == unique->id) { show(tr::lng_gift_wear_end_toast); } else if (id == unique->id) { show(tr::lng_gift_wear_start_toast); } *was = id; }; AddTableRow( table, tr::lng_gift_unique_owner(), MakePeerWithStatusValue(table, show, ownerId, handleChange), st::giveawayGiftCodePeerMargin); } else if (unique) { if (!unique->ownerName.isEmpty()) { AddTableRow( table, tr::lng_gift_unique_owner(), rpl::single(TextWithEntities{ unique->ownerName })); } else if (auto address = unique->ownerAddress; !address.isEmpty()) { auto label = MakeMaybeMultilineTokenValue(table, address, st); label->setClickHandlerFilter([=](const auto &...) { TextUtilities::SetClipboardText( TextForMimeData::Simple(FixupTransactionId(address))); show->showToast( tr::lng_gift_unique_address_copied(tr::now)); return false; }); AddTableRow( table, tr::lng_gift_unique_owner(), std::move(label)); } if (const auto hostId = PeerId(entry.bareGiftHostId)) { const auto was = std::make_shared>(); const auto handleChange = [=]( not_null badge, EmojiStatusId emojiStatusId) { const auto id = emojiStatusId.collectible ? emojiStatusId.collectible->id : 0; const auto show = [&](const auto &phrase) { showTooltip(badge, phrase( lt_name, rpl::single(tr::bold(UniqueGiftName(*unique))), tr::marked)); }; if (!*was || *was == id) { *was = id; return; } else if (*was == unique->id) { show(tr::lng_gift_wear_end_toast); } else if (id == unique->id) { show(tr::lng_gift_wear_start_toast); } *was = id; }; AddTableRow( table, tr::lng_gift_unique_telegram(), MakePeerWithStatusValue(table, show, hostId, handleChange), st::giveawayGiftCodePeerMargin); } } else if (giftToChannel) { AddTableRow( table, tr::lng_credits_box_history_entry_peer_in(), (entry.bareActorId ? MakePeerTableValue(table, show, PeerId(entry.bareActorId)) : MakeHiddenPeerTableValue(table)), st::giveawayGiftCodePeerMargin); if (entry.bareEntryOwnerId) { AddTableRow( table, tr::lng_credits_box_history_entry_peer(), MakePeerTableValue( table, show, PeerId(entry.bareEntryOwnerId)), st::giveawayGiftCodePeerMargin); } } else if (entry.auction && entry.bareGiftOwnerId) { AddTableRow( table, tr::lng_credits_box_history_entry_peer(), MakePeerTableValue( table, show, PeerId(entry.bareGiftOwnerId)), st::giveawayGiftCodePeerMargin); } else if (peerId && !giftToSelf) { const auto user = session->data().peer(peerId)->asUser(); const auto withSendButton = entry.in && user && !user->isBot(); auto send = withSendButton ? tr::lng_gift_send_small() : nullptr; auto handler = send ? Fn([=] { if (const auto window = show->resolveWindow()) { Ui::ShowStarGiftBox(window, user); } }) : nullptr; AddTableRow( table, tr::lng_credits_box_history_entry_peer_in(), MakePeerTableValue(table, show, peerId, send, handler), st::giveawayGiftCodePeerMargin); } else if (!entry.soldOutInfo) { AddTableRow( table, tr::lng_credits_box_history_entry_peer_in(), MakeHiddenPeerTableValue(table), st::giveawayGiftCodePeerMargin); } if (!unique && !entry.firstSaleDate.isNull()) { AddTableRow( table, tr::lng_gift_link_label_first_sale(), rpl::single(tr::marked( langDateTime(entry.firstSaleDate)))); } if (!unique && !entry.lastSaleDate.isNull()) { AddTableRow( table, tr::lng_gift_link_label_last_sale(), rpl::single(tr::marked( langDateTime(entry.lastSaleDate)))); } if (!unique && !entry.date.isNull()) { AddTableRow( table, tr::lng_gift_link_label_date(), rpl::single(tr::marked(langDateTime(entry.date)))); } if (unique) { const auto shared = entry.uniqueGift; AddUniqueGiftPropertyRows(container, show, table, shared, spinner); } else { AddTableRow( table, tr::lng_gift_link_label_value(), MakeStarGiftStarsValue( table, show, entry, std::move(convertToStars))); } if (entry.limitedCount > 0 && !entry.giftRefunded) { auto amount = rpl::single(TextWithEntities{ Lang::FormatCountDecimal(entry.limitedCount) }); const auto count = unique ? (entry.limitedCount - entry.limitedLeft) : entry.limitedLeft; AddTableRow( table, (unique ? tr::lng_gift_unique_availability_label() : tr::lng_gift_availability()), ((!unique && !count) ? tr::lng_gift_availability_none( lt_amount, std::move(amount), tr::marked) : (unique ? tr::lng_gift_unique_availability : tr::lng_gift_availability_left)( lt_count_decimal, rpl::single(count * 1.), lt_amount, std::move(amount), tr::marked))); } if (!unique && !entry.soldOutInfo && canStartUpgrade) { AddTableRow( table, tr::lng_gift_unique_status(), tr::lng_gift_unique_status_non(tr::marked)); } if (unique) { if (unique->value) { AddTableRow( table, tr::lng_gift_unique_value(), MakeUniqueGiftValueValue(table, show, entry, st)); } const auto &original = unique->originalDetails; if (original.recipientId) { const auto owner = &show->session().data(); const auto to = owner->peer(original.recipientId); const auto from = original.senderId ? owner->peer(original.senderId).get() : nullptr; const auto date = base::unixtime::parse(original.date).date(); const auto dateText = TextWithEntities{ langDayOfMonth(date) }; auto details = from ? (original.message.empty() ? tr::lng_gift_unique_info_sender( lt_from, rpl::single(tr::link(from->name(), 2)), lt_recipient, rpl::single(tr::link(to->name(), 1)), lt_date, rpl::single(dateText), tr::marked) : tr::lng_gift_unique_info_sender_comment( lt_from, rpl::single(tr::link(from->name(), 2)), lt_recipient, rpl::single(tr::link(to->name(), 1)), lt_date, rpl::single(dateText), lt_text, rpl::single(original.message), tr::marked)) : (original.message.empty() ? tr::lng_gift_unique_info_reciever( lt_recipient, rpl::single(tr::link(to->name(), 1)), lt_date, rpl::single(dateText), tr::marked) : tr::lng_gift_unique_info_reciever_comment( lt_recipient, rpl::single(tr::link(to->name(), 1)), lt_date, rpl::single(dateText), lt_text, rpl::single(original.message), tr::marked)); const auto tmp = std::make_shared(nullptr); auto made = MakeUniqueDetails( show, table, std::move(details), st, Core::TextContext({ .session = session }), entry.starsForDetailsRemove, std::move(removeDetails)); const auto showBoxLink = [=](not_null peer) { return std::make_shared([=] { show->showBox(PrepareShortInfoBox(peer, show)); }); }; made.label->setLink(1, showBoxLink(to)); if (from) { made.label->setLink(2, showBoxLink(from)); } made.label->setSelectable(true); *tmp = made.widget.data(); table->addRow( std::move(made.widget), nullptr, st::giveawayGiftCodeLabelMargin, st::giveawayGiftCodeValueMargin); } } else if (!entry.description.empty()) { auto label = object_ptr( table, rpl::single(entry.description), (st.tableValueMessage ? *st.tableValueMessage : st::giveawayGiftMessage), st::defaultPopupMenu, Core::TextContext({ .session = session })); label->setSelectable(true); table->addRow( nullptr, std::move(label), st::giveawayGiftCodeLabelMargin, st::giveawayGiftCodeValueMargin); } } void AddTransferGiftTable( std::shared_ptr show, not_null container, std::shared_ptr unique) { const auto table = container->add( object_ptr( container, st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); AddUniqueGiftPropertyRows(container, show, table, unique, nullptr); if (const auto value = unique->value.get()) { AddTableRow( table, tr::lng_gift_unique_value(), rpl::single( FormatValuePrice(value->valuePrice, value->currency, true))); } } void AddCreditsHistoryEntryTable( std::shared_ptr show, not_null container, Settings::CreditsEntryBoxStyleOverrides st, const Data::CreditsHistoryEntry &entry) { if (!entry) { return; } auto table = container->add( object_ptr( container, st.table ? *st.table : st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); const auto peerId = PeerId(entry.barePeerId); const auto actorId = PeerId(entry.bareActorId); const auto starrefRecipientId = PeerId(entry.starrefRecipientId); const auto session = &show->session(); if (entry.starrefCommission) { if (entry.giftResale && entry.starrefCommission < 1000) { const auto full = int(base::SafeRound(entry.credits.value() / (1. - (entry.starrefCommission / 1000.)))); auto value = Ui::Text::IconEmoji(&st::starIconEmojiColored); const auto starsText = Lang::FormatCreditsAmountDecimal( CreditsAmount{ full }); AddTableRow( table, tr::lng_credits_box_history_entry_gift_full_price(), rpl::single(value.append(' ' + starsText))); } else if (entry.starrefAmount) { AddTableRow( table, tr::lng_star_ref_commission_title(), rpl::single(TextWithEntities{ QString::number(entry.starrefCommission / 10.) + '%' })); } else { AddTableRow( table, tr::lng_gift_link_label_reason(), tr::lng_credits_box_history_entry_reason_star_ref( tr::marked)); } } if (starrefRecipientId && entry.starrefAmount && !entry.giftResale) { AddTableRow( table, tr::lng_credits_box_history_entry_affiliate(), show, starrefRecipientId); } if (peerId && entry.starrefCommission) { AddTableRow( table, (entry.giftResale ? tr::lng_credits_box_history_entry_gift_sold_to : entry.starrefAmount ? tr::lng_credits_box_history_entry_referred : tr::lng_credits_box_history_entry_miniapp)(), show, peerId); } if (!entry.postsSearch && (actorId || (!entry.starrefCommission && peerId))) { auto text = entry.starrefCommission ? tr::lng_credits_box_history_entry_referred() : entry.in ? tr::lng_credits_box_history_entry_peer_in() : entry.giftResale ? tr::lng_credits_box_history_entry_gift_bought_from() : entry.giftUpgraded ? tr::lng_credits_box_history_entry_gift_from() : tr::lng_credits_box_history_entry_peer(); const auto targetId = actorId ? actorId : peerId; const auto isPeerDefault = !entry.starrefCommission && !entry.in && !entry.giftResale && !entry.giftUpgraded; const auto user = isPeerDefault ? session->data().peer(targetId)->asUser() : nullptr; const auto withSendButton = user && !user->isInaccessible() && !user->isBot(); auto send = withSendButton ? tr::lng_gift_send_small() : nullptr; auto handler = send ? Fn([=] { if (const auto window = show->resolveWindow()) { Ui::ShowStarGiftBox(window, user); } }) : nullptr; AddTableRow( table, std::move(text), MakePeerTableValue(table, show, targetId, send, handler), st::giveawayGiftCodePeerMargin); } if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) { const auto peer = session->data().peer(peerId); if (const auto channel = peer->asBroadcast()) { const auto link = CreateMessageLink( session, peerId, entry.bareMsgId); auto label = object_ptr( table, rpl::single(tr::link(link)), table->st().defaultValue); label->setClickHandlerFilter([=](const auto &...) { if (const auto window = show->resolveWindow()) { window->showPeerHistory(channel, {}, msgId); } return false; }); AddTableRow( table, (entry.reaction ? tr::lng_credits_box_history_entry_message : tr::lng_credits_box_history_entry_media)(), std::move(label)); } } using Type = Data::CreditsHistoryEntry::PeerType; if (entry.peerType == Type::AppStore) { AddTableRow( table, tr::lng_credits_box_history_entry_via(), tr::lng_credits_box_history_entry_app_store( tr::rich)); } else if (entry.peerType == Type::PlayMarket) { AddTableRow( table, tr::lng_credits_box_history_entry_via(), tr::lng_credits_box_history_entry_play_market( tr::rich)); } else if (entry.peerType == Type::Fragment) { AddTableRow( table, (entry.gift ? tr::lng_credits_box_history_entry_peer_in : tr::lng_credits_box_history_entry_via)(), ((entry.gift && entry.credits.stars()) ? tr::lng_credits_box_history_entry_anonymous : tr::lng_credits_box_history_entry_fragment)( tr::rich)); } else if (entry.peerType == Type::Ads) { AddTableRow( table, tr::lng_credits_box_history_entry_via(), tr::lng_credits_box_history_entry_ads(tr::rich)); } else if (entry.peerType == Type::PremiumBot) { AddTableRow( table, tr::lng_credits_box_history_entry_via(), tr::lng_credits_box_history_entry_via_premium_bot( tr::rich)); } if (entry.bareGiveawayMsgId) { AddTableRow( table, tr::lng_gift_link_label_to(), show, show->session().userId()); } if (entry.bareGiveawayMsgId && entry.credits) { AddTableRow( table, tr::lng_gift_link_label_gift(), tr::lng_gift_stars_title( lt_count, rpl::single(entry.credits.value()), tr::rich)); } { const auto link = CreateMessageLink( session, peerId, entry.bareGiveawayMsgId); if (!link.isEmpty()) { AddTableRow( table, tr::lng_gift_link_label_reason(), tr::lng_gift_link_reason_giveaway( ) | rpl::map([link](const QString &text) { return tr::link(text, link); })); } } if (!entry.subscriptionUntil.isNull() && !entry.title.isEmpty()) { AddTableRow( table, tr::lng_gift_link_label_reason(), tr::lng_credits_box_history_entry_subscription( tr::marked)); } if (entry.paidMessagesAmount) { auto value = Ui::Text::IconEmoji(&st::starIconEmojiColored); const auto full = (entry.in ? 1 : -1) * (entry.credits + entry.paidMessagesAmount); const auto starsText = Lang::FormatCreditsAmountDecimal(full); AddTableRow( table, tr::lng_credits_paid_messages_full(), rpl::single(value.append(' ' + starsText))); } if (const auto months = entry.premiumMonthsForStars) { AddTableRow( table, tr::lng_credits_premium_gift_duration(), tr::lng_months( lt_count, rpl::single(1. * months), tr::marked)); } if (!entry.id.isEmpty()) { auto label = MakeMaybeMultilineTokenValue(table, entry.id, st); label->setClickHandlerFilter([=](const auto &...) { TextUtilities::SetClipboardText( TextForMimeData::Simple(FixupTransactionId(entry.id))); show->showToast( tr::lng_credits_box_history_entry_id_copied(tr::now)); return false; }); AddTableRow( table, tr::lng_credits_box_history_entry_id(), std::move(label)); } if (entry.floodSkip) { AddTableRow( table, tr::lng_credits_box_history_entry_floodskip_row(), rpl::single( tr::marked( Lang::FormatCountDecimal(entry.floodSkip)))); } if (!entry.date.isNull()) { AddTableRow( table, tr::lng_gift_link_label_date(), rpl::single(tr::marked(langDateTime(entry.date)))); } if (!entry.successDate.isNull()) { AddTableRow( table, tr::lng_credits_box_history_entry_success_date(), rpl::single(tr::marked(langDateTime(entry.date)))); } if (!entry.successLink.isEmpty()) { AddTableRow( table, tr::lng_credits_box_history_entry_success_url(), rpl::single( tr::link(entry.successLink, entry.successLink))); } if (entry.limitedCount > 0 && entry.limitedLeft >= 0) { AddTableRow( table, tr::lng_gift_availability(), tr::lng_gift_availability_left( lt_count_decimal, rpl::single(entry.limitedLeft) | tr::to_count(), lt_amount, rpl::single(TextWithEntities{ Lang::FormatCountDecimal(entry.limitedCount) }), tr::marked)); } } void AddSubscriptionEntryTable( std::shared_ptr show, not_null container, Settings::CreditsEntryBoxStyleOverrides st, const Data::SubscriptionEntry &s) { if (!s) { return; } auto table = container->add( object_ptr( container, st.table ? *st.table : st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); const auto peerId = PeerId(s.barePeerId); const auto user = peerIsUser(peerId) ? show->session().data().peer(peerId)->asUser() : nullptr; AddTableRow( table, (!s.title.isEmpty() && user && user->botInfo) ? tr::lng_credits_subscription_row_to_bot() : (!s.title.isEmpty() && user && !user->botInfo) ? tr::lng_credits_subscription_row_to_business() : tr::lng_credits_subscription_row_to(), show, peerId); if (!s.title.isEmpty()) { AddTableRow( table, tr::lng_credits_subscription_row_to(), rpl::single(tr::marked(s.title))); } if (!s.until.isNull()) { if (s.subscription.period > 0) { const auto subscribed = s.until.addSecs(-s.subscription.period); if (subscribed.isValid()) { AddTableRow( table, tr::lng_group_invite_joined_row_date(), rpl::single( tr::marked(langDateTime(subscribed)))); } } AddTableRow( table, s.expired ? tr::lng_credits_subscription_row_next_none() : s.cancelled ? tr::lng_credits_subscription_row_next_off() : tr::lng_credits_subscription_row_next_on(), rpl::single(tr::marked(langDateTime(s.until)))); } } void AddSubscriberEntryTable( std::shared_ptr show, not_null container, Settings::CreditsEntryBoxStyleOverrides st, not_null peer, TimeId date) { auto table = container->add( object_ptr( container, st.table ? *st.table : st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); AddTableRow( table, tr::lng_group_invite_joined_row_subscriber(), show, peer->id); if (const auto d = base::unixtime::parse(date); !d.isNull()) { AddTableRow( table, tr::lng_group_invite_joined_row_date(), rpl::single(tr::marked(langDateTime(d)))); } } void AddCreditsBoostTable( std::shared_ptr show, not_null container, Settings::CreditsEntryBoxStyleOverrides st, const Data::Boost &b) { auto table = container->add( object_ptr( container, st.table ? *st.table : st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); const auto peerId = b.giveawayMessage.peer; if (!peerId) { return; } const auto from = show->session().data().peer(peerId); AddTableRow( table, tr::lng_credits_box_history_entry_peer_in(), show, from->id); if (b.credits) { AddTableRow( table, tr::lng_gift_link_label_gift(), tr::lng_gift_stars_title( lt_count, rpl::single(float64(b.credits)), tr::rich)); } { const auto link = CreateMessageLink( &show->session(), peerId, b.giveawayMessage.msg.bare); if (!link.isEmpty()) { AddTableRow( table, tr::lng_gift_link_label_reason(), tr::lng_gift_link_reason_giveaway( ) | rpl::map([link](const QString &text) { return tr::link(text, link); })); } } if (!b.date.isNull()) { AddTableRow( table, tr::lng_gift_link_label_date(), rpl::single(tr::marked(langDateTime(b.date)))); } if (!b.expiresAt.isNull()) { AddTableRow( table, tr::lng_gift_until(), rpl::single(tr::marked(langDateTime(b.expiresAt)))); } } void AddChannelEarnTable( std::shared_ptr show, not_null container, const Data::CreditsHistoryEntry &entry) { const auto table = container->add( object_ptr( container, st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); if (!entry.id.isEmpty()) { auto label = MakeMaybeMultilineTokenValue(table, entry.id, {}); label->setClickHandlerFilter([=](const auto &...) { TextUtilities::SetClipboardText( TextForMimeData::Simple(FixupTransactionId(entry.id))); show->showToast( tr::lng_credits_box_history_entry_id_copied(tr::now)); return false; }); AddTableRow( table, tr::lng_credits_box_history_entry_id(), std::move(label)); } } void AddUniqueGiftValueTable( std::shared_ptr show, not_null container, Settings::CreditsEntryBoxStyleOverrides st, const Data::CreditsHistoryEntry &entry) { const auto value = entry.uniqueGift ? entry.uniqueGift->value : nullptr; auto table = container->add( object_ptr( container, st.table ? *st.table : st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); if (value->initialSaleDate) { AddTableRow( table, tr::lng_gift_value_initial_sale(), rpl::single(FormatValueDate(value->initialSaleDate))); } auto helper = Ui::Text::CustomEmojiHelper(); auto starIcon = helper.paletteDependent( Ui::Earn::IconCreditsEmoji()); AddTableRow( table, tr::lng_gift_value_initial_price(), tr::lng_gift_value_initial_price_value( lt_stars, rpl::single(starIcon.append(' ').append( Lang::FormatCreditsAmountDecimal(value->initialPriceStars) )), lt_amount, rpl::single(FormatValuePrice( value->initialSalePrice, value->currency, true)), tr::marked), helper.context()); if (value->lastSaleDate) { AddTableRow( table, tr::lng_gift_value_last_sale(), rpl::single(FormatValueDate(value->lastSaleDate))); } if (value->lastSalePrice) { AddTableRow( table, tr::lng_gift_value_last_price(), MakePriceWithChangePercentValue(table, value)); } const auto tooltip = std::make_shared( TableRowTooltipData{ .parent = container }); if (value->minimumPrice) { AddTableRow( table, tr::lng_gift_value_minimum_price(), MakeMinimumPriceValue(table, tooltip, entry.uniqueGift)); } if (value->averagePrice) { AddTableRow( table, tr::lng_gift_vlaue_average_price(), MakeAveragePriceValue(table, tooltip, entry.uniqueGift)); } }