diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 86f628a7e6..608b536586 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2348,6 +2348,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_gift_premium_about" = "Subscription for exclusive Telegram features."; "lng_action_gift_refunded" = "This gift was downgraded because a request to refund the payment related to this gift was made, and the money was returned."; "lng_action_gift_got_ton" = "Use TON to suggest posts to channels."; +"lng_action_gift_offer" = "{user} offered you {cost} for {name}."; +"lng_action_gift_offer_you" = "You offered {cost} for {name}."; +"lng_action_gift_offer_state_expires" = "This offer expires in {time}."; +"lng_action_gift_offer_state_accepted" = "This offer was accepted."; +"lng_action_gift_offer_state_rejected" = "This offer was rejected."; +"lng_action_gift_offer_state_expired" = "This offer has expired."; +"lng_action_gift_offer_sold" = "{user} sold {name} for {cost}."; +"lng_action_gift_offer_sold_you" = "You sold {name} for {cost}."; +"lng_action_gift_offer_decline" = "Reject"; +"lng_action_gift_offer_accept" = "Accept"; +"lng_action_gift_offer_expired" = "The offer from {user} to buy your {name} for {cost} has expired."; +"lng_action_gift_offer_expired_you" = "Your offer to buy {name} for {cost} has expired."; +"lng_action_gift_offer_declined" = "{user} rejected your offer to buy {name} for {cost}."; +"lng_action_gift_offer_declined_you" = "You rejected {user}'s offer to buy your {name} for {cost}."; "lng_action_suggested_photo_me" = "You suggested this photo for {user}'s Telegram profile."; "lng_action_suggested_photo" = "{user} suggests this photo for your Telegram profile."; "lng_action_suggested_photo_button" = "View Photo"; @@ -3936,6 +3950,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_transfer_unlist" = "Unlist"; "lng_gift_transfer_locked_title" = "Action Locked"; "lng_gift_transfer_locked_text" = "Transfer this gift to your Telegram account on Fragment to unlock this action."; +"lng_gift_offer_button" = "Offer to Buy"; +"lng_gift_offer_title" = "Offer to Buy"; +"lng_gift_offer_stars_about" = "Choose how many Stars you'd like to offer for {name}."; +"lng_gift_offer_ton_about" = "Choose how many TON you'd like to offer for {name}."; +"lng_gift_offer_duration" = "Offer Duration"; +"lng_gift_offer_duration_about" = "Choose how long {user} can accept your offer. When the time expires, the amount will be refunded."; +"lng_gift_offer_cost_button" = "Offer {cost}"; "lng_gift_sell_unlist_title" = "Unlist {name}"; "lng_gift_sell_unlist_sure" = "Are you sure you want to unlist your gift?"; "lng_gift_sell_title" = "Price in Stars"; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index d2cc09afa1..1f061cc49b 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -930,6 +930,7 @@ std::optional FromTL( .themeUser = themeUser, .nanoTonForResale = FindTonForResale(data.vresell_amount()), .starsForResale = FindStarsForResale(data.vresell_amount()), + .starsMinOffer = data.voffer_min_stars().value_or(-1), .number = data.vnum().v, .onlyAcceptTon = data.is_resale_ton_only(), .canBeTheme = data.is_theme_available(), diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index 16d42851ab..cb0d327a0d 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -3560,6 +3560,63 @@ void ShowUniqueGiftSellBox( }); } +void SendOfferBuyGift( + std::shared_ptr show, + std::shared_ptr unique, + SuggestPostOptions options, + Fn done) { + const auto randomId = base::RandomValue(); + const auto owner = show->session().data().peer(unique->ownerId); + + using Flag = MTPpayments_SendStarGiftOffer::Flag; + show->session().api().request(MTPpayments_SendStarGiftOffer( + MTP_flags(Flag() | Flag()),//Flag::f_allow_paid_stars) + owner->input, + MTP_string(unique->slug), + StarsAmountToTL(options.price()), + MTP_int(options.offerDuration), + MTP_long(randomId), + MTP_long(0) // allow_paid_stars + )).done([=](const MTPUpdates &result) { + show->session().api().applyUpdates(result); + done(); + }).fail([=](const MTP::Error &error) { + if (error.type() == u""_q) { + + } else { + show->showToast(error.type()); + } + }).send(); +} + +void ShowOfferBuyBox( + std::shared_ptr show, + std::shared_ptr unique) { + Expects(unique->starsMinOffer >= 0); + + const auto weak = std::make_shared>(); + const auto done = [=](SuggestPostOptions result) { + SendOfferBuyGift(show, unique, result, [=] { + if (const auto strong = weak->get()) { + strong->closeBox(); + } + }); + }; + using namespace HistoryView; + const auto options = SuggestPostOptions{ + .exists = 1, + .priceWhole = uint32(unique->starsMinOffer), + }; + auto priceBox = Box(ChooseSuggestPriceBox, SuggestPriceBoxArgs{ + .peer = show->session().data().peer(unique->ownerId), + .done = done, + .value = options, + .mode = SuggestMode::Gift, + }); + *weak = priceBox.data(); + show->show(std::move(priceBox)); +} + void GiftReleasedByHandler(not_null peer) { const auto session = &peer->session(); const auto window = session->tryResolveWindow(peer); diff --git a/Telegram/SourceFiles/boxes/star_gift_box.h b/Telegram/SourceFiles/boxes/star_gift_box.h index e767e13493..41d70204d1 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.h +++ b/Telegram/SourceFiles/boxes/star_gift_box.h @@ -95,6 +95,10 @@ void ShowUniqueGiftSellBox( Data::SavedStarGiftId savedId, Settings::GiftWearBoxStyleOverride st); +void ShowOfferBuyBox( + std::shared_ptr show, + std::shared_ptr unique); + void GiftReleasedByHandler(not_null peer); struct StarGiftUpgradeArgs { diff --git a/Telegram/SourceFiles/data/data_msg_id.h b/Telegram/SourceFiles/data/data_msg_id.h index 3627fd4d0c..c7ee478618 100644 --- a/Telegram/SourceFiles/data/data_msg_id.h +++ b/Telegram/SourceFiles/data/data_msg_id.h @@ -213,6 +213,7 @@ struct SuggestPostOptions { uint32 priceNano : 31 = 0; uint32 ton : 1 = 0; TimeId date = 0; + TimeId offerDuration = 0; [[nodiscard]] CreditsAmount price() const { return CreditsAmount( diff --git a/Telegram/SourceFiles/data/data_star_gift.h b/Telegram/SourceFiles/data/data_star_gift.h index e2a7aa6ac6..a23208637c 100644 --- a/Telegram/SourceFiles/data/data_star_gift.h +++ b/Telegram/SourceFiles/data/data_star_gift.h @@ -76,6 +76,7 @@ struct UniqueGift { int64 nanoTonForResale = -1; int starsForResale = -1; int starsForTransfer = -1; + int starsMinOffer = -1; int number = 0; bool onlyAcceptTon = false; bool canBeTheme = false; diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 31790abc90..a25a384152 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -905,6 +905,20 @@ UserpicsSlice ParseUserpicsSlice( return result; } +[[nodiscard]] ActionStarGift ParseStarGift(const MTPStarGift &gift) { + return gift.match([&](const MTPDstarGift &gift) { + return ActionStarGift{ + .giftId = uint64(gift.vid().v), + .stars = int64(gift.vstars().v), + .limited = gift.is_limited(), + }; + }, [&](const MTPDstarGiftUnique &gift) { + return ActionStarGift{ + .giftId = uint64(gift.vid().v), + }; + }); +} + File &Story::file() { return media.file(); } @@ -1744,41 +1758,16 @@ ServiceAction ParseServiceAction( .isUnclaimed = data.is_unclaimed(), }; }, [&](const MTPDmessageActionStarGift &data) { - data.vgift().match([&](const MTPDstarGift &gift) { - result.content = ActionStarGift{ - .giftId = uint64(gift.vid().v), - .stars = int64(gift.vstars().v), - .text = (data.vmessage() - ? ParseText( - data.vmessage()->data().vtext(), - data.vmessage()->data().ventities().v) - : std::vector()), - .anonymous = data.is_name_hidden(), - .limited = gift.is_limited(), - }; - }, [&](const MTPDstarGiftUnique &gift) { - result.content = ActionStarGift{ - .giftId = uint64(gift.vid().v), - .text = (data.vmessage() - ? ParseText( - data.vmessage()->data().vtext(), - data.vmessage()->data().ventities().v) - : std::vector()), - .anonymous = data.is_name_hidden(), - }; - }); + auto content = ParseStarGift(data.vgift()); + content.text = (data.vmessage() + ? ParseText( + data.vmessage()->data().vtext(), + data.vmessage()->data().ventities().v) + : std::vector()); + content.anonymous = data.is_name_hidden(); + result.content = content; }, [&](const MTPDmessageActionStarGiftUnique &data) { - data.vgift().match([&](const MTPDstarGift &gift) { - result.content = ActionStarGift{ - .giftId = uint64(gift.vid().v), - .stars = int64(gift.vstars().v), - .limited = gift.is_limited(), - }; - }, [&](const MTPDstarGiftUnique &gift) { - result.content = ActionStarGift{ - .giftId = uint64(gift.vid().v), - }; - }); + result.content = ParseStarGift(data.vgift()); }, [&](const MTPDmessageActionPaidMessagesRefunded &data) { result.content = ActionPaidMessagesRefunded{ .messages = data.vcount().v, @@ -1844,6 +1833,21 @@ ServiceAction ParseServiceAction( fields.vmonth().v, fields.vyear().value_or_empty()); result.content = content; + }, [&](const MTPDmessageActionStarGiftPurchaseOffer &data) { + auto content = ParseStarGift(data.vgift()); + content.offer = true; + content.offerPrice = CreditsAmountFromTL(data.vprice()); + content.offerExpireAt = data.vexpires_at().v; + content.offerAccepted = data.is_accepted(); + content.offerDeclined = data.is_declined(); + result.content = content; + }, [&](const MTPDmessageActionStarGiftPurchaseOfferDeclined &data) { + auto content = ParseStarGift(data.vgift()); + content.offer = true; + content.offerDeclined = true; + content.offerExpired = data.is_expired(); + content.offerPrice = CreditsAmountFromTL(data.vprice()); + result.content = content; }, [](const MTPDmessageActionEmpty &data) {}); return result; } diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 278f6946ec..8a4d0060a7 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -690,6 +690,13 @@ struct ActionStarGift { std::vector text; bool anonymous = false; bool limited = false; + + CreditsAmount offerPrice; + TimeId offerExpireAt = 0; + bool offer = false; + bool offerAccepted = false; + bool offerDeclined = false; + bool offerExpired = false; }; struct ActionPaidMessagesRefunded { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 78adad3962..ee7f939712 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -6393,7 +6393,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { Unexpected("PhoneCall type in setServiceMessageFromMtp."); }; - auto prepareSuggestBirthday = [this](const MTPDmessageActionSuggestBirthday &action) { + auto prepareSuggestBirthday = [&](const MTPDmessageActionSuggestBirthday &action) { auto result = PreparedServiceText{}; const auto isSelf = (_from->id == _from->session().userPeerId()); const auto peer = isSelf ? history()->peer : _from; @@ -6412,6 +6412,16 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return result; }; + auto prepareStarGiftPurchaseOffer = [&](const MTPDmessageActionStarGiftPurchaseOffer &action) { + auto result = PreparedServiceText{}; + return result; + }; + + auto prepareStarGiftPurchaseOfferDeclined = [&](const MTPDmessageActionStarGiftPurchaseOfferDeclined &action) { + auto result = PreparedServiceText{}; + return result; + }; + setServiceText(action.match( prepareChatAddUserText, prepareChatJoinedByLink, @@ -6469,6 +6479,8 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { prepareSuggestedPostSuccess, prepareSuggestedPostRefund, prepareSuggestBirthday, + prepareStarGiftPurchaseOffer, + prepareStarGiftPurchaseOfferDeclined, PrepareEmptyText, PrepareErrorText)); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.cpp index ba7f91fa1a..680e2f92dd 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "ui/boxes/choose_date_time.h" +#include "ui/boxes/single_choice_box.h" #include "ui/controls/ton_common.h" #include "ui/widgets/fields/number_input.h" #include "ui/widgets/fields/input_field.h" @@ -401,6 +402,7 @@ void ChooseSuggestPriceBox( rpl::event_stream<> fieldsChanges; rpl::variable price; rpl::variable date; + rpl::variable offerDuration; rpl::variable ton; Fn()> computePrice; Fn save; @@ -413,6 +415,7 @@ void ChooseSuggestPriceBox( state->price = args.value.price(); const auto peer = args.peer; + const auto mode = args.mode; const auto admin = peer->amMonoforumAdmin(); const auto broadcast = peer->monoforumBroadcast(); const auto usePeer = broadcast ? broadcast : peer; @@ -426,7 +429,9 @@ void ChooseSuggestPriceBox( box->setStyle(st::suggestPriceBox); - auto title = (args.mode == SuggestMode::New) + auto title = (mode == SuggestMode::Gift) + ? tr::lng_gift_offer_title() + : (mode == SuggestMode::New) ? tr::lng_suggest_options_title() : tr::lng_suggest_options_change(); if (admin) { @@ -567,17 +572,27 @@ void ChooseSuggestPriceBox( auto starsAbout = admin ? rpl::combine( youGet(StarsPriceValue(state->price.value()), true), - tr::lng_suggest_options_stars_warning(Ui::Text::RichLangValue) + tr::lng_suggest_options_stars_warning(tr::rich) ) | rpl::map([=](const QString &t1, const TextWithEntities &t2) { return TextWithEntities{ t1 }.append("\n\n").append(t2); }) - : tr::lng_suggest_options_stars_price_about(Ui::Text::WithEntities); + : (mode == SuggestMode::Gift) + ? tr::lng_gift_offer_stars_about( + lt_name, + rpl::single(tr::marked(args.giftName)), + tr::rich) + : tr::lng_suggest_options_stars_price_about(tr::rich); auto tonAbout = admin ? youGet( TonPriceValue(state->price.value()), false - ) | Ui::Text::ToWithEntities() - : tr::lng_suggest_options_ton_price_about(Ui::Text::WithEntities); + ) | rpl::map(tr::rich) + : (mode == SuggestMode::Gift) + ? tr::lng_gift_offer_ton_about( + lt_name, + rpl::single(tr::marked(args.giftName)), + tr::rich) + : tr::lng_suggest_options_ton_price_about(tr::rich); auto priceInput = AddStarsTonPriceInput(container, { .session = session, .showTon = state->ton.value(), @@ -595,39 +610,94 @@ void ChooseSuggestPriceBox( Ui::AddSkip(container); - const auto time = Settings::AddButtonWithLabel( - container, - tr::lng_suggest_options_date(), - state->date.value() | rpl::map([](TimeId date) { - return date - ? langDateTime(base::unixtime::parse(date)) - : tr::lng_suggest_options_date_any(tr::now); - }), - st::settingsButtonNoIcon); - - time->setClickedCallback([=] { - const auto weak = std::make_shared>(); - const auto parentWeak = base::make_weak(box); - const auto done = [=](TimeId result) { - if (parentWeak) { - state->date = result; - } - if (const auto strong = weak->get()) { - strong->closeBox(); - } + if (args.mode == SuggestMode::Gift) { + const auto day = 86400; + auto durations = std::vector{ + day / 4, + day / 2, + day, + day + day / 2, + day * 2, + day * 3, }; - auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{ - .session = session, - .done = done, - .value = state->date.current(), - .mode = args.mode, - }); - *weak = dateBox.data(); - box->uiShow()->show(std::move(dateBox)); - }); + if (peer->session().isTestMode()) { + durations.insert(begin(durations), 120); + } + const auto durationToText = [](TimeId date) { + return (date >= 3600) + ? tr::lng_hours(tr::now, lt_count, date / 3600) + : tr::lng_minutes(tr::now, lt_count, date / 60); + }; + state->offerDuration = day; + const auto duration = Settings::AddButtonWithLabel( + container, + tr::lng_gift_offer_duration(), + state->offerDuration.value() | rpl::map(durationToText), + st::settingsButtonNoIcon); - Ui::AddSkip(container); - Ui::AddDividerText(container, tr::lng_suggest_options_date_about()); + duration->setClickedCallback([=] { + box->uiShow()->show(Box([=](not_null box) { + const auto save = [=](int index) { + state->offerDuration = durations[index]; + }; + auto options = durations + | ranges::views::transform(durationToText) + | ranges::to_vector; + const auto selected = ranges::find( + durations, + state->offerDuration.current()); + SingleChoiceBox(box, { + .title = tr::lng_settings_angle_backend(), + .options = options, + .initialSelection = int(selected - begin(durations)), + .callback = save, + }); + })); + }); + + Ui::AddSkip(container); + Ui::AddDividerText( + container, + tr::lng_gift_offer_duration_about( + lt_user, + rpl::single(peer->shortName()))); + } else { + const auto time = Settings::AddButtonWithLabel( + container, + tr::lng_suggest_options_date(), + state->date.value() | rpl::map([](TimeId date) { + return date + ? langDateTime(base::unixtime::parse(date)) + : tr::lng_suggest_options_date_any(tr::now); + }), + st::settingsButtonNoIcon); + + time->setClickedCallback([=] { + const auto weak = std::make_shared< + base::weak_qptr + >(); + const auto parentWeak = base::make_weak(box); + const auto done = [=](TimeId result) { + if (parentWeak) { + state->date = result; + } + if (const auto strong = weak->get()) { + strong->closeBox(); + } + }; + auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{ + .session = session, + .done = done, + .value = state->date.current(), + .mode = args.mode, + }); + *weak = dateBox.data(); + box->uiShow()->show(std::move(dateBox)); + }); + + Ui::AddSkip(container); + Ui::AddDividerText(container, tr::lng_suggest_options_date_about()); + } state->save = [=] { const auto ton = uint32(state->ton.current() ? 1 : 0); @@ -674,6 +744,7 @@ void ChooseSuggestPriceBox( .priceNano = uint32(value.nano()), .ton = ton, .date = state->date.current(), + .offerDuration = state->offerDuration.current(), }); }; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.h b/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.h index 8fd8356a13..d663c46b46 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.h @@ -34,6 +34,7 @@ enum class SuggestMode { New, Change, Publish, + Gift, }; struct SuggestTimeBoxArgs { @@ -91,6 +92,7 @@ struct SuggestPriceBoxArgs { Fn done; SuggestPostOptions value; SuggestMode mode = SuggestMode::New; + QString giftName; }; void ChooseSuggestPriceBox( not_null box, diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style index 8779aaf9e6..a45b5409d7 100644 --- a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style +++ b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style @@ -235,6 +235,7 @@ darkGiftHide: icon {{ "menu/stealth", groupCallMembersFg }}; darkGiftShow: icon {{ "menu/show_in_chat", groupCallMembersFg }}; darkGiftPin: icon {{ "menu/pin", groupCallMembersFg }}; darkGiftUnpin: icon {{ "menu/unpin", groupCallMembersFg }}; +darkGiftOffer: icon {{ "menu/earn", groupCallMembersFg }}; darkGiftPalette: TextPalette(defaultTextPalette) { linkFg: mediaviewTextLinkFg; monoFg: groupCallMembersFg; diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 3c9f9241c9..176cdcadf7 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -187,7 +187,7 @@ messageActionPaymentRefunded#41b3e202 flags:# peer:Peer currency:string total_am messageActionGiftStars#45d5b021 flags:# currency:string amount:long stars:long crypto_currency:flags.0?string crypto_amount:flags.0?long transaction_id:flags.1?string = MessageAction; messageActionPrizeStars#b00c47a2 flags:# unclaimed:flags.0?true stars:long transaction_id:string boost_peer:Peer giveaway_msg_id:int = MessageAction; messageActionStarGift#db596550 flags:# name_hidden:flags.0?true saved:flags.2?true converted:flags.3?true upgraded:flags.5?true refunded:flags.9?true can_upgrade:flags.10?true prepaid_upgrade:flags.13?true upgrade_separate:flags.16?true auction_acquired:flags.17?true gift:StarGift message:flags.1?TextWithEntities convert_stars:flags.4?long upgrade_msg_id:flags.5?int upgrade_stars:flags.8?long from_id:flags.11?Peer peer:flags.12?Peer saved_id:flags.12?long prepaid_upgrade_hash:flags.14?string gift_msg_id:flags.15?int to_id:flags.18?Peer = MessageAction; -messageActionStarGiftUnique#95728543 flags:# upgrade:flags.0?true transferred:flags.1?true saved:flags.2?true refunded:flags.5?true prepaid_upgrade:flags.11?true assigned:flags.13?true gift:StarGift can_export_at:flags.3?int transfer_stars:flags.4?long from_id:flags.6?Peer peer:flags.7?Peer saved_id:flags.7?long resale_amount:flags.8?StarsAmount can_transfer_at:flags.9?int can_resell_at:flags.10?int drop_original_details_stars:flags.12?long = MessageAction; +messageActionStarGiftUnique#95728543 flags:# upgrade:flags.0?true transferred:flags.1?true saved:flags.2?true refunded:flags.5?true prepaid_upgrade:flags.11?true assigned:flags.13?true from_offer:flags.14?true gift:StarGift can_export_at:flags.3?int transfer_stars:flags.4?long from_id:flags.6?Peer peer:flags.7?Peer saved_id:flags.7?long resale_amount:flags.8?StarsAmount can_transfer_at:flags.9?int can_resell_at:flags.10?int drop_original_details_stars:flags.12?long = MessageAction; messageActionPaidMessagesRefunded#ac1f1fcd count:int stars:long = MessageAction; messageActionPaidMessagesPrice#84b88578 flags:# broadcast_messages_allowed:flags.0?true stars:long = MessageAction; messageActionConferenceCall#2ffe2f7a flags:# missed:flags.0?true active:flags.1?true video:flags.4?true call_id:long duration:flags.2?int other_participants:flags.3?Vector = MessageAction; @@ -198,6 +198,8 @@ messageActionSuggestedPostSuccess#95ddcf69 price:StarsAmount = MessageAction; messageActionSuggestedPostRefund#69f916f8 flags:# payer_initiated:flags.0?true = MessageAction; messageActionGiftTon#a8a3c699 flags:# currency:string amount:long crypto_currency:string crypto_amount:long transaction_id:flags.0?string = MessageAction; messageActionSuggestBirthday#2c8f2a25 birthday:Birthday = MessageAction; +messageActionStarGiftPurchaseOffer#774278d4 flags:# accepted:flags.0?true declined:flags.1?true gift:StarGift price:StarsAmount expires_at:int = MessageAction; +messageActionStarGiftPurchaseOfferDeclined#73ada76b flags:# expired:flags.0?true gift:StarGift price:StarsAmount = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -1915,7 +1917,7 @@ starsGiveawayOption#94ce852a flags:# extended:flags.0?true default:flags.1?true starsGiveawayWinnersOption#54236209 flags:# default:flags.0?true users:int per_user_stars:long = StarsGiveawayWinnersOption; starGift#1b9a4d7f flags:# limited:flags.0?true sold_out:flags.1?true birthday:flags.2?true require_premium:flags.7?true limited_per_user:flags.8?true peer_color_available:flags.10?true auction:flags.11?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int availability_resale:flags.4?long convert_stars:long first_sale_date:flags.1?int last_sale_date:flags.1?int upgrade_stars:flags.3?long resell_min_stars:flags.4?long title:flags.5?string released_by:flags.6?Peer per_user_total:flags.8?int per_user_remains:flags.8?int locked_until_date:flags.9?int auction_slug:flags.11?string gifts_per_round:flags.11?int = StarGift; -starGiftUnique#b0bf741b flags:# require_premium:flags.6?true resale_ton_only:flags.7?true theme_available:flags.9?true id:long gift_id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector availability_issued:int availability_total:int gift_address:flags.3?string resell_amount:flags.4?Vector released_by:flags.5?Peer value_amount:flags.8?long value_currency:flags.8?string theme_peer:flags.10?Peer peer_color:flags.11?PeerColor host_id:flags.12?Peer = StarGift; +starGiftUnique#9f2fb096 flags:# require_premium:flags.6?true resale_ton_only:flags.7?true theme_available:flags.9?true id:long gift_id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector availability_issued:int availability_total:int gift_address:flags.3?string resell_amount:flags.4?Vector released_by:flags.5?Peer value_amount:flags.8?long value_currency:flags.8?string theme_peer:flags.10?Peer peer_color:flags.11?PeerColor host_id:flags.12?Peer offer_min_stars:flags.13?int = StarGift; payments.starGiftsNotModified#a388a368 = payments.StarGifts; payments.starGifts#2ed82995 hash:int gifts:Vector chats:Vector users:Vector = payments.StarGifts; @@ -2732,6 +2734,8 @@ payments.checkCanSendGift#c0c4edc9 gift_id:long = payments.CheckCanSendGiftResul payments.getStarGiftAuctionState#5c9ff4d6 auction:InputStarGiftAuction version:int = payments.StarGiftAuctionState; payments.getStarGiftAuctionAcquiredGifts#6ba2cbec gift_id:long = payments.StarGiftAuctionAcquiredGifts; payments.getStarGiftActiveAuctions#a5d0514d hash:long = payments.StarGiftActiveAuctions; +payments.resolveStarGiftOffer#e9ce781c flags:# decline:flags.0?true offer_msg_id:int = Updates; +payments.sendStarGiftOffer#8fb86b41 flags:# peer:InputPeer slug:string price:StarsAmount duration:int random_id:long allow_paid_stars:flags.0?long = Updates; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; @@ -2867,4 +2871,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo; -// LAYER 219 +// LAYER 220 diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 179b6657c8..396433fbef 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -964,6 +964,21 @@ void ProcessReceivedSubscriptions( // (owner->isChannel() && owner->asChannel()->canTransferGifts()); } +[[nodiscard]] bool ShowOfferBuyButton( + not_null session, + const Data::CreditsHistoryEntry &e) { + const auto unique = e.uniqueGift.get(); + const auto owner = (unique && unique->ownerId) + ? session->data().peer(unique->ownerId).get() + : nullptr; + return owner + && owner->isUser() + && !owner->isSelf() + && (unique->starsMinOffer >= 0); + // Currently we're not making offers for channel gifts. + // (owner->isChannel() && !owner->asChannel()->canTransferGifts()); +} + void FillUniqueGiftMenu( std::shared_ptr show, not_null menu, @@ -1158,6 +1173,10 @@ void FillUniqueGiftMenu( })); }, st.unlist ? st.unlist : &st::menuIconTagRemove); } + } else if (ShowOfferBuyButton(&show->session(), e)) { + menu->addAction(tr::lng_gift_offer_button(tr::now), [=] { + ShowOfferBuyBox(show, unique); + }, st.offer ? st.offer : &st::menuIconEarn); } } @@ -1193,6 +1212,7 @@ CreditsEntryBoxStyleOverrides DarkCreditsEntryBoxStyle() { .hide = &st::darkGiftHide, .pin = &st::darkGiftPin, .unpin = &st::darkGiftUnpin, + .offer = &st::darkGiftOffer, .shareBox = std::make_shared( DarkShareBoxStyle()), .giftWearBox = std::make_shared( diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 4df951e40c..2b7d35d69d 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -127,6 +127,7 @@ struct CreditsEntryBoxStyleOverrides { const style::icon *hide = nullptr; const style::icon *pin = nullptr; const style::icon *unpin = nullptr; + const style::icon *offer = nullptr; std::shared_ptr shareBox; std::shared_ptr giftWearBox; };