diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 7b609c610d..f7ac2a9715 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1642,6 +1642,8 @@ PRIVATE tde2e/tde2e_integration.h ui/boxes/edit_invite_link_session.cpp ui/boxes/edit_invite_link_session.h + ui/boxes/emoji_stake_box.cpp + ui/boxes/emoji_stake_box.h ui/boxes/peer_qr_box.cpp ui/boxes/peer_qr_box.h ui/chat/attach/attach_item_single_file_preview.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 2d6174a667..70dfc14f29 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2467,6 +2467,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_not_enough_funds" = "Transaction failed."; "lng_you_paid_stars#one" = "You paid {count} Star."; "lng_you_paid_stars#other" = "You paid {count} Stars."; +"lng_action_stake_game_loading" = "Trying luck in a game..."; +"lng_action_stake_game_nothing" = "{from} didn't win anything"; +"lng_action_stake_game_nothing_you" = "You didn't win anything"; +"lng_action_stake_game_won" = "{from} won {amount}"; +"lng_action_stake_game_won_you" = "You won {amount}"; + +"lng_stake_game_title" = "Emoji Stake"; +"lng_stake_game_beta" = "Beta"; +"lng_stake_game_about" = "A limited play-test of the upcoming emoji mini-game platform for a small group of users."; +"lng_stake_game_results" = "Results and Returns"; +"lng_stake_game_resets" = "A streak resets after 3 sixes or a stake change."; +"lng_stake_game_your" = "Your stake"; +"lng_stake_game_save_and_roll" = "Save and Roll"; "lng_you_joined_group" = "You joined this group"; diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index 44233354be..abe8686890 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -28,6 +28,8 @@ struct SendOptions { TimeId scheduleRepeatPeriod = 0; BusinessShortcutId shortcutId = 0; EffectId effectId = 0; + QByteArray stakeSeedHash; + int64 stakeNanoTon = 0; int starsApproved = 0; bool silent = false; bool handleSupportSwitch = false; diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 949a81757f..eeb79a702b 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -347,7 +347,7 @@ bool SendDice(MessageToSend &message) { || !message.textWithTags.tags.isEmpty()) { return false; } - auto &config = message.action.history->session().appConfig(); + //auto &config = message.action.history->session().appConfig(); static const auto hardcoded = std::vector{ Stickers::DicePacks::kDiceString, Stickers::DicePacks::kDartString, @@ -356,9 +356,9 @@ bool SendDice(MessageToSend &message) { Stickers::DicePacks::kFballString + QChar(0xFE0F), Stickers::DicePacks::kBballString, }; - const auto list = config.get>( + const auto list = hardcoded/*config.get>( "emojies_send_dice", - hardcoded); + hardcoded)*/; AssertIsDebug(); const auto emoji = full.toString(); if (!ranges::contains(list, emoji)) { return false; @@ -372,7 +372,6 @@ bool SendDice(MessageToSend &message) { message.action.clearDraft = false; message.action.generateLocal = true; - auto &action = message.action; api->sendAction(action); @@ -428,6 +427,11 @@ bool SendDice(MessageToSend &message) { session->data().registerMessageRandomId(randomId, newId); + auto seed = QByteArray(32, Qt::Uninitialized); + base::RandomFill(bytes::make_detached_span(seed)); + const auto stake = action.options.stakeSeedHash.isEmpty() + ? 0 + : action.options.stakeNanoTon; history->addNewLocalMessage({ .id = newId.msg, .flags = flags, @@ -440,10 +444,12 @@ bool SendDice(MessageToSend &message) { .effectId = action.options.effectId, .suggest = HistoryMessageSuggestInfo(action.options), }, TextWithEntities(), MTP_messageMediaDice( - MTP_flags(0), + MTP_flags(stake + ? MTPDmessageMediaDice::Flag::f_game_outcome + : MTPDmessageMediaDice::Flag()), MTP_int(0), MTP_string(emoji), - MTPmessages_EmojiGameOutcome())); + MTP_messages_emojiGameOutcome(MTP_bytes(seed), MTP_long(0)))); histories.sendPreparedMessage( history, action.replyTo, @@ -452,7 +458,12 @@ bool SendDice(MessageToSend &message) { MTP_flags(sendFlags), peer->input(), Data::Histories::ReplyToPlaceholder(), - MTP_inputMediaDice(MTP_string(emoji)), + (stake + ? MTP_inputMediaStakeDice( + MTP_bytes(action.options.stakeSeedHash), + MTP_long(stake), + MTP_bytes(seed)) + : MTP_inputMediaDice(MTP_string(emoji))), MTP_string(), MTP_long(randomId), MTPReplyMarkup(), diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 1a70668671..23aad8641f 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/mtp_instance.h" #include "mtproto/mtproto_config.h" #include "mtproto/mtproto_dc_options.h" +#include "chat_helpers/stickers_dice_pack.h" #include "data/business/data_shortcut_messages.h" #include "data/components/credits.h" #include "data/components/gift_auctions.h" @@ -2677,6 +2678,11 @@ void Updates::feedUpdate(const MTPUpdate &update) { const auto &data = update.c_updateStarGiftAuctionUserState(); _session->giftAuctions().apply(data); } break; + + case mtpc_updateEmojiGameInfo: { + const auto &data = update.c_updateEmojiGameInfo(); + _session->diceStickersPacks().apply(data); + } break; } } diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 773696db57..cbd131dab4 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -548,6 +548,8 @@ void ApiWrap::sendMessageFail( } } peer->updateFull(); + } else if (show) { + show->showToast(error); } if (const auto item = _session->data().message(itemId)) { Assert(randomId != 0); diff --git a/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp b/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp index b0f212d94a..5e6ead6fb5 100644 --- a/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp @@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_credits_graphics.h" #include "storage/storage_account.h" #include "ui/boxes/confirm_box.h" +#include "ui/boxes/emoji_stake_box.h" // AddStarsInputField #include "ui/controls/button_labels.h" #include "ui/controls/feature_list.h" #include "ui/controls/table_rows.h" @@ -603,7 +604,7 @@ void EditCustomBid( box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); }); - const auto starsField = HistoryView::AddStarsInputField(container, { + const auto starsField = AddStarsInputField(container, { .value = current, }); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp index 8941f35aa8..3c986eadfa 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "chat_helpers/stickers_lottie.h" +#include "data/data_media_types.h" #include "data/data_session.h" #include "data/data_document.h" #include "base/unixtime.h" @@ -144,4 +145,54 @@ DocumentData *DicePacks::lookup(const QString &emoji, int value) { ).first->second->lookup(value); } +void DicePacks::resolveGameOptions( + Fn done) { + _resolveGameOptionsCallback = std::move(done); + if (_resolveGameOptionsRequestId) { + return; + } + + _resolveGameOptionsRequestId = _session->api().request( + MTPmessages_GetEmojiGameInfo() + ).done([=](const MTPmessages_EmojiGameInfo &result) { + _resolveGameOptionsRequestId = 0; + if (const auto onstack = base::take(_resolveGameOptionsCallback)) { + onstack(result.match([&]( + const MTPDmessages_emojiGameUnavailable &) { + return Data::DiceGameOptions(); + }, [&](const MTPDmessages_emojiGameDiceInfo &data) { + auto jackpot = 0; + auto rewards = std::array{}; + const auto ¶ms = data.vparams().v; + const auto count = int(params.size()); + for (auto i = 0; i != count; ++i) { + if (i < 6) { + rewards[i] = params[i].v; + } else if (i == 6) { + jackpot = params[i].v; + } else { + break; + } + } + return Data::DiceGameOptions{ + .seedHash = data.vgame_hash().v, + .previousSteakNanoTon = int64(data.vprev_stake().v), + .milliRewards = rewards, + .jackpotMilliReward = jackpot, + .currentStreak = data.vcurrent_streak().v, + .playsLeft = data.vplays_left().value_or_empty(), + }; + })); + } + }).fail([=] { + _resolveGameOptionsRequestId = 0; + if (const auto onstack = base::take(_resolveGameOptionsCallback)) { + onstack({}); + } + }).send(); +} + +void DicePacks::apply(const MTPDupdateEmojiGameInfo &update) { +} + } // namespace Stickers diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h index cb5210c351..249467e7f2 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h @@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class DocumentData; +namespace Data { +struct DiceGameOptions; +} // namespace Data + namespace Main { class Session; } // namespace Main @@ -51,11 +55,17 @@ public: [[nodiscard]] DocumentData *lookup(const QString &emoji, int value); + void resolveGameOptions(Fn done); + void apply(const MTPDupdateEmojiGameInfo &update); + private: const not_null _session; base::flat_map> _packs; + mtpRequestId _resolveGameOptionsRequestId = 0; + Fn _resolveGameOptionsCallback; + }; } // namespace Stickers diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 4b5ae3ec78..f1407dadfe 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_unique_gift.h" #include "history/view/media/history_view_userpic_suggestion.h" #include "dialogs/ui/dialogs_message_view.h" +#include "ui/boxes/emoji_stake_box.h" #include "ui/image/image.h" #include "ui/effects/spoiler_mess.h" #include "ui/text/format_song_document_name.h" @@ -2420,14 +2421,19 @@ std::unique_ptr MediaTodoList::createView( replacing); } -MediaDice::MediaDice(not_null parent, QString emoji, int value) +MediaDice::MediaDice( + not_null parent, + DiceGameOutcome outcome, + QString emoji, + int value) : Media(parent) +, _outcome(outcome) , _emoji(emoji) , _value(value) { } std::unique_ptr MediaDice::clone(not_null parent) { - return std::make_unique(parent, _emoji, _value); + return std::make_unique(parent, _outcome, _emoji, _value); } QString MediaDice::emoji() const { @@ -2438,6 +2444,10 @@ int MediaDice::value() const { return _value; } +DiceGameOutcome MediaDice::outcome() const { + return _outcome; +} + bool MediaDice::allowsRevoke(TimeId now) const { const auto peer = parent()->history()->peer; if (peer->isSelf() || !peer->isUser()) { @@ -2470,8 +2480,18 @@ bool MediaDice::updateSentMedia(const MTPMessageMedia &media) { if (media.type() != mtpc_messageMediaDice) { return false; } - _value = media.c_messageMediaDice().vvalue().v; - parent()->history()->owner().requestItemRepaint(parent()); + const auto &data = media.c_messageMediaDice(); + _value = data.vvalue().v; + if (const auto outcome = data.vgame_outcome()) { + const auto &data = outcome->data(); + _outcome = Data::DiceGameOutcome{ + .nanoTon = int64(data.vton_amount().v), + .seed = data.vseed().v, + }; + } else { + _outcome = {}; + } + parent()->history()->owner().notifyItemDataChange(parent()); return true; } @@ -2504,6 +2524,8 @@ ClickHandlerPtr MediaDice::MakeHandler( } }; return std::make_shared([=](ClickContext context) { + const auto my = context.other.value(); + const auto weak = my.sessionWindow; auto config = Ui::Toast::Config{ .text = { tr::lng_about_random(tr::now, lt_emoji, emoji) }, .st = &st::historyDiceToast, @@ -2517,13 +2539,38 @@ ClickHandlerPtr MediaDice::MakeHandler( config.filter = crl::guard(&history->session(), [=]( const ClickHandlerPtr &handler, Qt::MouseButton button) { + const auto pack = &history->session().diceStickersPacks(); if (button == Qt::LeftButton && !ShownToast.empty()) { - auto message = Api::MessageToSend( - Api::SendAction(history)); - message.action.clearDraft = false; - message.textWithTags.text = emoji; + pack->resolveGameOptions([=]( + const Data::DiceGameOptions &options) { + const auto window = weak.get(); + const auto seedHash = options.seedHash; + const auto sendWithStake = [=](int64 stakeNanoTon) { + auto message = Api::MessageToSend( + Api::SendAction(history)); + message.textWithTags.text = emoji; - Api::SendDice(message); + auto &action = message.action; + action.clearDraft = false; + + auto &options = action.options; + options.stakeNanoTon = stakeNanoTon; + options.stakeSeedHash = seedHash; + + Api::SendDice(message); + }; + if (!options || !window) { + sendWithStake(0); + } else { + window->show(Ui::MakeEmojiGameStakeBox({ + .session = &window->session(), + .currentStake = options.previousSteakNanoTon, + .milliRewards = options.milliRewards, + .jackpotMilliReward = options.jackpotMilliReward, + .submit = sendWithStake, + })); + } + }); HideExisting(); } return false; @@ -2531,8 +2578,6 @@ ClickHandlerPtr MediaDice::MakeHandler( } HideExisting(); - const auto my = context.other.value(); - const auto weak = my.sessionWindow; if (const auto strong = weak.get()) { ShownToast = strong->showToast(std::move(config)); } else { diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index d0d60fc200..442d092a75 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -653,14 +653,41 @@ private: }; +struct DiceGameOptions { + QByteArray seedHash; + int64 previousSteakNanoTon = 0; + std::array milliRewards; + int jackpotMilliReward = 0; + int currentStreak = 0; + int playsLeft = 0; + + explicit operator bool() const { + return !seedHash.isEmpty(); + } +}; + +struct DiceGameOutcome { + int64 nanoTon = 0; + QByteArray seed; + + explicit operator bool() const { + return !seed.isEmpty(); + } +}; + class MediaDice final : public Media { public: - MediaDice(not_null parent, QString emoji, int value); + MediaDice( + not_null parent, + DiceGameOutcome outcome, + QString emoji, + int value); std::unique_ptr clone(not_null parent) override; [[nodiscard]] QString emoji() const; [[nodiscard]] int value() const; + [[nodiscard]] DiceGameOutcome outcome() const; bool allowsRevoke(TimeId now) const override; TextWithEntities notificationText() const override; @@ -681,6 +708,7 @@ public: const QString &emoji); private: + DiceGameOutcome _outcome; QString _emoji; int _value = 0; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index ed81a52348..fc0e19f5f9 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -366,8 +366,15 @@ std::unique_ptr HistoryItem::CreateMedia( item, item->history()->owner().processTodoList(item->fullId(), media)); }, [&](const MTPDmessageMediaDice &media) -> Result { + auto outcome = Data::DiceGameOutcome(); + if (const auto game = media.vgame_outcome()) { + const auto &data = game->data(); + outcome.seed = data.vseed().v; + outcome.nanoTon = data.vton_amount().v; + } return std::make_unique( item, + outcome, qs(media.vemoticon()), media.vvalue().v); }, [&](const MTPDmessageMediaStory &media) -> Result { 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 51147d5c3f..72279f5964 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.cpp @@ -32,6 +32,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/emoji_stake_box.h" #include "ui/boxes/single_choice_box.h" #include "ui/controls/ton_common.h" #include "ui/widgets/fields/number_input.h" @@ -75,19 +76,6 @@ namespace { })); } -[[nodiscard]] not_null AddMoneyInputIcon( - not_null parent, - Ui::Text::PaletteDependentEmoji emoji) { - auto helper = Ui::Text::CustomEmojiHelper(); - auto text = helper.paletteDependent(std::move(emoji)); - return Ui::CreateChild( - parent, - rpl::single(std::move(text)), - st::defaultFlatLabel, - st::defaultPopupMenu, - helper.context()); -} - } // namespace void ChooseSuggestTimeBox( @@ -153,60 +141,6 @@ void AddApproximateUsd( usd->widthValue() | rpl::on_next(move, usd->lifetime()); } -not_null AddStarsInputField( - not_null container, - StarsInputFieldArgs &&args) { - const auto wrap = container->add( - object_ptr( - container, - st::editTagField.heightMin), - st::boxRowPadding); - const auto result = Ui::CreateChild( - wrap, - st::editTagField, - rpl::single(u"0"_q), - args.value ? QString::number(*args.value) : QString(), - args.max ? args.max : std::numeric_limits::max()); - const auto icon = AddMoneyInputIcon( - result, - Ui::Earn::IconCreditsEmoji()); - - wrap->widthValue() | rpl::on_next([=](int width) { - icon->move(st::starsFieldIconPosition); - result->move(0, 0); - result->resize(width, result->height()); - wrap->resize(width, result->height()); - }, wrap->lifetime()); - - return result; -} - -not_null AddTonInputField( - not_null container, - TonInputFieldArgs &&args) { - const auto wrap = container->add( - object_ptr( - container, - st::editTagField.heightMin), - st::boxRowPadding); - const auto result = Ui::CreateTonAmountInput( - wrap, - rpl::single('0' + Ui::TonAmountSeparator() + '0'), - args.value); - const auto icon = AddMoneyInputIcon( - result, - Ui::Earn::IconCurrencyEmoji()); - - wrap->widthValue() | rpl::on_next([=](int width) { - icon->move(st::tonFieldIconPosition); - result->move(0, 0); - result->resize(width, result->height()); - wrap->resize(width, result->height()); - }, wrap->lifetime()); - - return result; -} - StarsTonPriceInput AddStarsTonPriceInput( not_null container, StarsTonPriceArgs &&args) { 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 91c186c9d8..80f523bf5d 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.h @@ -51,21 +51,6 @@ void ChooseSuggestTimeBox( not_null box, SuggestTimeBoxArgs &&args); -struct StarsInputFieldArgs { - std::optional value; - int64 max = 0; -}; -[[nodiscard]] not_null AddStarsInputField( - not_null container, - StarsInputFieldArgs &&args); - -struct TonInputFieldArgs { - int64 value = 0; -}; -[[nodiscard]] not_null AddTonInputField( - not_null container, - TonInputFieldArgs &&args); - struct StarsTonPriceInput { Fn focusCallback; Fn()> computeResult; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 481b162aba..d6c179b7cf 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -3278,8 +3278,9 @@ bool Message::updateBottomInfo() { void Message::itemDataChanged() { const auto infoChanged = updateBottomInfo(); const auto reactionsChanged = updateReactions(); - - if (infoChanged || reactionsChanged) { + const auto media = this->media(); + const auto mediaChanged = media && media->updateItemData(); + if (infoChanged || reactionsChanged || mediaChanged) { history()->owner().requestViewResize(this); } else { repaint(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp index 0ce2500b49..c65242d903 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp @@ -12,7 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "history/history_item_components.h" +#include "history/history_item_helpers.h" #include "history/view/history_view_element.h" +#include "lang/lang_keys.h" #include "main/main_session.h" #include "styles/style_chat.h" @@ -42,10 +44,53 @@ Dice::Dice(not_null parent, not_null dice) if (_showLastFrame) { _drawingEnd = true; } + + if (const auto outcome = _dice->outcome()) { + _outcomeSet = true; + _outcomeNanoTon = outcome.nanoTon; + updateOutcomeMessage(); + } } Dice::~Dice() = default; +void Dice::updateOutcomeMessage() { + const auto item = _parent->data(); + const auto from = item->from(); + const auto out = item->out() || from->isSelf(); + const auto amount = tr::marked(QString::fromUtf8("\xf0\x9f\x92\x8e") + + " " + + QString::number(_outcomeNanoTon / 1e9)); + const auto text = _outcomeNanoTon + ? (out + ? tr::lng_action_stake_game_won_you( + tr::now, + lt_amount, + amount, + tr::marked) + : tr::lng_action_stake_game_won( + tr::now, + lt_from, + tr::link(st::wrap_rtl(from->name()), 1), + lt_amount, + amount, + tr::marked)) + : !_dice->value() + ? tr::lng_action_stake_game_loading(tr::now, tr::marked) + : (out + ? tr::lng_action_stake_game_nothing_you(tr::now, tr::marked) + : tr::lng_action_stake_game_nothing( + tr::now, + lt_from, + tr::link(st::wrap_rtl(from->name()), 1), + tr::marked)); + auto prepared = PreparedServiceText{ text }; + if (!out) { + prepared.links.push_back(from->createOpenLink()); + } + _parent->setServicePreMessage(prepared, _link); +} + QSize Dice::countOptimalSize() { return _start ? _start->countOptimalSize() : Sticker::EmojiSize(); } @@ -54,6 +99,19 @@ ClickHandlerPtr Dice::link() { return _link; } +bool Dice::updateItemData() { + const auto outcome = _dice->outcome(); + const auto outcomeSet = !!outcome; + const auto outcomeNanoTon = outcomeSet ? outcome.nanoTon : 0; + if (_outcomeSet == outcomeSet && _outcomeNanoTon == outcomeNanoTon) { + return false; + } + _outcomeSet = outcomeSet; + _outcomeNanoTon = outcomeNanoTon; + updateOutcomeMessage(); + return true; +} + void Dice::draw(Painter &p, const PaintContext &context, const QRect &r) { if (!_start) { if (const auto document = Lookup(_parent, _dice->emoji(), 0)) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.h b/Telegram/SourceFiles/history/view/media/history_view_dice.h index 68a5368d0f..a3292b0b64 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_dice.h +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.h @@ -42,14 +42,20 @@ public: } } + bool updateItemData() override; + private: + void updateOutcomeMessage(); + const not_null _parent; const not_null _dice; ClickHandlerPtr _link; std::optional _start; std::optional _end; + int64 _outcomeNanoTon = false; mutable bool _showLastFrame = false; mutable bool _drawingEnd = false; + bool _outcomeSet = false; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index adf66c7c3d..1cf8f4747e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -354,6 +354,9 @@ public: [[nodiscard]] virtual QMargins bubbleRollRepaintMargins() const { return QMargins(); } + virtual bool updateItemData() { + return false; + } virtual void paintBubbleFireworks( Painter &p, const QRect &bubble, diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h index 0259392d6f..bf2c406c87 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h @@ -58,6 +58,9 @@ public: virtual bool hasTextForCopy() const { return false; } + virtual bool updateItemData() { + return false; + } virtual ~Content() = default; }; @@ -93,6 +96,10 @@ public: std::optional reactionButtonCenterOverride() const override; QPoint resolveCustomInfoRightBottom() const override; + bool updateItemData() override { + return _content->updateItemData(); + } + void stickerClearLoopPlayed() override { _content->stickerClearLoopPlayed(); } diff --git a/Telegram/SourceFiles/ui/boxes/emoji_stake_box.cpp b/Telegram/SourceFiles/ui/boxes/emoji_stake_box.cpp new file mode 100644 index 0000000000..f29dd06375 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/emoji_stake_box.cpp @@ -0,0 +1,224 @@ +/* +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/boxes/emoji_stake_box.h" + +#include "base/object_ptr.h" +#include "data/components/credits.h" +#include "info/channel_statistics/earn/earn_icons.h" +#include "lang/lang_keys.h" +#include "lottie/lottie_icon.h" +#include "main/main_session.h" +#include "settings/settings_common.h" // CreateLottieIcon +#include "settings/settings_credits_graphics.h" // AddBalanceWidget +#include "ui/controls/ton_common.h" +#include "ui/text/custom_emoji_helper.h" +#include "ui/text/custom_emoji_text_badge.h" +#include "ui/widgets/fields/input_field.h" +#include "ui/widgets/fields/number_input.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/padding_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "ui/vertical_list.h" +#include "styles/style_calls.h" // confcallJoinBox +#include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" +#include "styles/style_credits.h" // creditsHistoryRightSkip +#include "styles/style_layers.h" +#include "styles/style_settings.h" // settingsCloudPasswordIconSize +#include "styles/style_widgets.h" + +namespace Ui { +namespace { + +[[nodiscard]] not_null AddMoneyInputIcon( + not_null parent, + Text::PaletteDependentEmoji emoji) { + auto helper = Text::CustomEmojiHelper(); + auto text = helper.paletteDependent(std::move(emoji)); + return CreateChild( + parent, + rpl::single(std::move(text)), + st::defaultFlatLabel, + st::defaultPopupMenu, + helper.context()); +} + +[[nodiscard]] object_ptr MakeLogo(not_null box) { + const auto &size = st::settingsCloudPasswordIconSize; + auto icon = Settings::CreateLottieIcon( + box->verticalLayout(), + { .name = u"dice_idle"_q, .sizeOverride = { size, size } }, + st::settingLocalPasscodeIconPadding); + const auto animate = std::move(icon.animate); + box->showFinishes() | rpl::take(1) | rpl::on_next([=] { + animate(anim::repeat::loop); + }, box->lifetime()); + return std::move(icon.widget); +} + +} // namespace + +not_null AddStarsInputField( + not_null container, + StarsInputFieldArgs &&args) { + const auto wrap = container->add( + object_ptr( + container, + st::editTagField.heightMin), + st::boxRowPadding); + const auto result = CreateChild( + wrap, + st::editTagField, + rpl::single(u"0"_q), + args.value ? QString::number(*args.value) : QString(), + args.max ? args.max : std::numeric_limits::max()); + const auto icon = AddMoneyInputIcon( + result, + Earn::IconCreditsEmoji()); + + wrap->widthValue() | rpl::on_next([=](int width) { + icon->move(st::starsFieldIconPosition); + result->move(0, 0); + result->resize(width, result->height()); + wrap->resize(width, result->height()); + }, wrap->lifetime()); + + return result; +} + +not_null AddTonInputField( + not_null container, + TonInputFieldArgs &&args) { + const auto wrap = container->add( + object_ptr( + container, + st::editTagField.heightMin), + st::boxRowPadding); + const auto result = CreateTonAmountInput( + wrap, + rpl::single('0' + TonAmountSeparator() + '0'), + args.value); + const auto icon = AddMoneyInputIcon( + result, + Earn::IconCurrencyEmoji()); + + wrap->widthValue() | rpl::on_next([=](int width) { + icon->move(st::tonFieldIconPosition); + result->move(0, 0); + result->resize(width, result->height()); + wrap->resize(width, result->height()); + }, wrap->lifetime()); + + return result; +} + +void EmojiGameStakeBox( + not_null box, + EmojiGameStakeArgs &&args) { + box->setStyle(st::confcallJoinBox); + box->setWidth(st::boxWideWidth); + box->setNoContentMargin(true); + + box->addRow( + MakeLogo(box), + st::boxRowPadding + st::confcallLinkHeaderIconPadding, + style::al_top); + + auto helper = Text::CustomEmojiHelper(); + const auto beta = helper.paletteDependent( + Text::CustomEmojiTextBadge( + tr::lng_stake_game_beta(tr::now), + st::customEmojiTextBadge)); + auto title = tr::lng_stake_game_title( + tr::marked + ) | rpl::map([=](TextWithEntities &&text) { + return text.append(' ').append(beta); + }); + box->addRow( + object_ptr( + box, + std::move(title), + st::boxTitle, + st::defaultPopupMenu, + helper.context()), + st::boxRowPadding + st::confcallLinkTitlePadding, + style::al_top); + box->addRow( + object_ptr( + box, + tr::lng_stake_game_about(tr::rich), + st::confcallLinkCenteredText), + st::boxRowPadding, + style::al_top + )->setTryMakeSimilarLines(true); + + const auto container = box->verticalLayout(); + + AddSubsectionTitle(container, tr::lng_stake_game_results()); + + const auto close = CreateChild( + container, + st::boxTitleClose); + close->setClickedCallback([=] { box->closeBox(); }); + container->widthValue() | rpl::on_next([=](int) { + close->moveToRight(0, 0); + }, close->lifetime()); + + const auto session = args.session; + session->credits().tonLoad(true); + const auto balance = Settings::AddBalanceWidget( + container, + session, + session->credits().tonBalanceValue(), + false); + rpl::combine( + balance->sizeValue(), + container->sizeValue() + ) | rpl::on_next([=](const QSize &, const QSize &) { + balance->moveToLeft( + st::creditsHistoryRightSkip * 2, + st::creditsHistoryRightSkip); + balance->update(); + }, balance->lifetime()); + + box->addRow( + object_ptr( + box, + tr::lng_stake_game_resets(tr::rich), + st::confcallLinkCenteredText), + st::boxRowPadding, + style::al_top + )->setTryMakeSimilarLines(true); + + AddSubsectionTitle(container, tr::lng_stake_game_your()); + const auto field = AddTonInputField(container, { + .value = args.currentStake, + }); + + const auto submit = args.submit; + const auto button = box->addButton(rpl::single(QString()), [=] { + const auto text = field->getLastText(); + const auto now = Ui::ParseTonAmountString(text); + if (!now) { + field->showError(); + } else { + box->closeBox(); + submit(*now); + } + }); + + button->setText(tr::lng_stake_game_save_and_roll( + ) | rpl::map([=](QString text) { + return tr::marked( + QString::fromUtf8("\xf0\x9f\x8e\xb2") + ).append(' ').append(text); + })); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/boxes/emoji_stake_box.h b/Telegram/SourceFiles/ui/boxes/emoji_stake_box.h new file mode 100644 index 0000000000..3b5133582b --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/emoji_stake_box.h @@ -0,0 +1,50 @@ +/* +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 + +#include "ui/layers/generic_box.h" + +namespace Main { +class Session; +} // namespace Main + +namespace Ui { + +class InputField; +class NumberInput; +class VerticalLayout; + +struct StarsInputFieldArgs { + std::optional value; + int64 max = 0; +}; +[[nodiscard]] not_null AddStarsInputField( + not_null container, + StarsInputFieldArgs &&args); + +struct TonInputFieldArgs { + int64 value = 0; +}; +[[nodiscard]] not_null AddTonInputField( + not_null container, + TonInputFieldArgs &&args); + +struct EmojiGameStakeArgs { + not_null session; + int64 currentStake = 0; + std::array milliRewards; + int jackpotMilliReward = 0; + Fn submit; +}; +void EmojiGameStakeBox(not_null box, EmojiGameStakeArgs &&args); +[[nodiscard]] inline object_ptr MakeEmojiGameStakeBox( + EmojiGameStakeArgs &&args) { + return Box(EmojiGameStakeBox, std::move(args)); +} + +} // namespace Ui