diff --git a/Telegram/Resources/animations/dice/dice_1.tgs b/Telegram/Resources/animations/dice/dice_1.tgs new file mode 100644 index 0000000000..36506f7108 Binary files /dev/null and b/Telegram/Resources/animations/dice/dice_1.tgs differ diff --git a/Telegram/Resources/animations/dice/dice_2.tgs b/Telegram/Resources/animations/dice/dice_2.tgs new file mode 100644 index 0000000000..ac0de6ff5a Binary files /dev/null and b/Telegram/Resources/animations/dice/dice_2.tgs differ diff --git a/Telegram/Resources/animations/dice/dice_3.tgs b/Telegram/Resources/animations/dice/dice_3.tgs new file mode 100644 index 0000000000..1362b087fe Binary files /dev/null and b/Telegram/Resources/animations/dice/dice_3.tgs differ diff --git a/Telegram/Resources/animations/dice/dice_4.tgs b/Telegram/Resources/animations/dice/dice_4.tgs new file mode 100644 index 0000000000..60b7c6d8ce Binary files /dev/null and b/Telegram/Resources/animations/dice/dice_4.tgs differ diff --git a/Telegram/Resources/animations/dice/dice_5.tgs b/Telegram/Resources/animations/dice/dice_5.tgs new file mode 100644 index 0000000000..77bb708982 Binary files /dev/null and b/Telegram/Resources/animations/dice/dice_5.tgs differ diff --git a/Telegram/Resources/animations/dice/dice_6.tgs b/Telegram/Resources/animations/dice/dice_6.tgs new file mode 100644 index 0000000000..f151a226e7 Binary files /dev/null and b/Telegram/Resources/animations/dice/dice_6.tgs differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 70dfc14f29..b9410dfe57 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2477,7 +2477,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "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_resets" = "A streak resets after 3 {emoji} or a stake change."; "lng_stake_game_your" = "Your stake"; "lng_stake_game_save_and_roll" = "Save and Roll"; diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc index 87ba31c8c9..6c7e4c94c7 100644 --- a/Telegram/Resources/qrc/telegram/animations.qrc +++ b/Telegram/Resources/qrc/telegram/animations.qrc @@ -67,6 +67,13 @@ ../../animations/dice/slot_pull.tgs ../../animations/dice/winners.tgs + ../../animations/dice/dice_1.tgs + ../../animations/dice/dice_2.tgs + ../../animations/dice/dice_3.tgs + ../../animations/dice/dice_4.tgs + ../../animations/dice/dice_5.tgs + ../../animations/dice/dice_6.tgs + ../../animations/star_reaction/appear.tgs ../../animations/star_reaction/center.tgs ../../animations/star_reaction/select.tgs diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp index c65242d903..9d39c39ae9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp @@ -47,6 +47,7 @@ Dice::Dice(not_null parent, not_null dice) if (const auto outcome = _dice->outcome()) { _outcomeSet = true; + _outcomeValue = _dice->value(); _outcomeNanoTon = outcome.nanoTon; updateOutcomeMessage(); } @@ -75,7 +76,7 @@ void Dice::updateOutcomeMessage() { lt_amount, amount, tr::marked)) - : !_dice->value() + : !_outcomeValue ? tr::lng_action_stake_game_loading(tr::now, tr::marked) : (out ? tr::lng_action_stake_game_nothing_you(tr::now, tr::marked) @@ -103,11 +104,15 @@ bool Dice::updateItemData() { const auto outcome = _dice->outcome(); const auto outcomeSet = !!outcome; const auto outcomeNanoTon = outcomeSet ? outcome.nanoTon : 0; - if (_outcomeSet == outcomeSet && _outcomeNanoTon == outcomeNanoTon) { + const auto outcomeValue = _dice->value(); + if (_outcomeSet == outcomeSet + && _outcomeNanoTon == outcomeNanoTon + && _outcomeValue == outcomeValue) { return false; } _outcomeSet = outcomeSet; _outcomeNanoTon = outcomeNanoTon; + _outcomeValue = outcomeValue; updateOutcomeMessage(); return true; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.h b/Telegram/SourceFiles/history/view/media/history_view_dice.h index a3292b0b64..37d7238fb2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_dice.h +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.h @@ -53,6 +53,7 @@ private: std::optional _start; std::optional _end; int64 _outcomeNanoTon = false; + int _outcomeValue = 0; mutable bool _showLastFrame = false; mutable bool _drawingEnd = false; bool _outcomeSet = false; diff --git a/Telegram/SourceFiles/ui/boxes/emoji_stake_box.cpp b/Telegram/SourceFiles/ui/boxes/emoji_stake_box.cpp index f29dd06375..7f64486d3a 100644 --- a/Telegram/SourceFiles/ui/boxes/emoji_stake_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/emoji_stake_box.cpp @@ -8,7 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/emoji_stake_box.h" #include "base/object_ptr.h" +#include "chat_helpers/stickers_lottie.h" #include "data/components/credits.h" +#include "data/stickers/data_custom_emoji.h" +#include "data/data_document.h" +#include "data/data_session.h" #include "info/channel_statistics/earn/earn_icons.h" #include "lang/lang_keys.h" #include "lottie/lottie_icon.h" @@ -18,12 +22,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/controls/ton_common.h" #include "ui/text/custom_emoji_helper.h" #include "ui/text/custom_emoji_text_badge.h" +#include "ui/text/text_lottie_custom_emoji.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/painter.h" #include "ui/vertical_list.h" #include "styles/style_calls.h" // confcallJoinBox #include "styles/style_chat.h" @@ -53,15 +59,140 @@ namespace { const auto &size = st::settingsCloudPasswordIconSize; auto icon = Settings::CreateLottieIcon( box->verticalLayout(), - { .name = u"dice_idle"_q, .sizeOverride = { size, size } }, + { .name = u"dice_6"_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); + animate(anim::repeat::once); }, box->lifetime()); return std::move(icon.widget); } +[[nodiscard]] object_ptr MakeTable( + not_null parent, + const EmojiGameStakeArgs &args) { + auto result = object_ptr(parent); + const auto raw = result.data(); + + struct State { + std::array, 6> dices; + std::array multiplicators; + }; + const auto state = raw->lifetime().make_state(); + const auto serialize = [&](int milliReward) { + return QString(QChar(0xD7)) + QString::number(milliReward / 1000.); + }; + const auto esize = st::largeEmojiSize; + for (auto i = 0; i != 6; ++i) { + state->dices[i] = Lottie::MakeEmoji({ + .name = u"dice_%1"_q.arg(i + 1), + .sizeOverride = QSize(esize, esize), + }, [=] { raw->update(); }); + const auto value = (i < args.milliRewards.size()) + ? args.milliRewards[i] + : 0; + state->multiplicators[i].setText( + st::semiboldTextStyle, + serialize(value)); + } + state->multiplicators[6].setText( + st::semiboldTextStyle, + serialize(args.jackpotMilliReward)); + + raw->paintOn([=](QPainter &p) { + auto path = QPainterPath(); + const auto &st = st::defaultTable; + const auto border = st.border; + const auto half = border / 2.; + const auto add = QMargins(border, border, border, border); + const auto inner = raw->rect().marginsRemoved(add); + path.addRoundedRect(inner, st.radius, st.radius); + { + const auto y = border + inner.height() / 2.; + path.moveTo(border + half, y); + path.lineTo(border + inner.width() - half, y); + } + { + const auto x = border + inner.width() / 2.; + path.moveTo(x, border + half); + path.lineTo(x, border + inner.height() - half); + } + { + const auto x = border + (inner.width() - border) / 4.; + path.moveTo(x, border + half); + path.lineTo(x, border + inner.height() - half); + } + { + const auto x = border + + (inner.width() / 2.) + + (inner.width() - border) / 4.; + path.moveTo(x, border + half); + path.lineTo(x, border + (inner.height() / 2.) - half); + } + auto pen = st.borderFg->p; + pen.setWidth(st.border); + p.setPen(pen); + p.setBrush(Qt::NoBrush); + + auto hq = PainterHighQualityEnabler(p); + p.drawPath(path); + + const auto etop = -style::ConvertScale(10); + const auto ttop = etop + esize; + { + auto left = border + half; + const auto width = (inner.width() - 3 * border) / 4.; + auto top = border + half; + const auto now = crl::now(); + for (auto i = 0; i != 7; ++i, left += width + border) { + if (i == 4) { + left -= 4 * (width + border); + top += border + half + inner.height() / 2.; + } + const auto x = int(base::SafeRound(left)); + const auto y = int(base::SafeRound(top)); + const auto right = (i < 6) + ? int(base::SafeRound(left + width)) + : int(base::SafeRound(left + 2 * width + border)); + const auto w = (right - x); + + if (i < 6) { + state->dices[i]->paint(p, { + .textColor = st::windowBoldFg->c, + .now = now, + .position = QPoint(x + (w - esize) / 2, y + etop), + .paused = true, + .internal = { .forceLastFrame = true } + }); + } else { + const auto e = state->dices.back().get(); + for (auto j = 0; j != 3; ++j) { + e->paint(p, { + .textColor = st::windowBoldFg->c, + .now = now, + .position = QPoint( + x + (w - 3 * esize) / 2 + (esize * j), + y + etop), + .paused = true, + .internal = { .forceLastFrame = true } + }); + } + } + + p.setPen(st::windowBoldFg); + const auto &mul = state->multiplicators[i]; + mul.draw(p, { + .position = { x + (w - mul.maxWidth()) / 2, y + ttop }, + }); + } + } + }); + raw->widthValue() | rpl::on_next([=](int width) { + raw->resize(width, width / 3); + }, raw->lifetime()); + return result; +} + } // namespace not_null AddStarsInputField( @@ -124,10 +255,12 @@ void EmojiGameStakeBox( box->setStyle(st::confcallJoinBox); box->setWidth(st::boxWideWidth); box->setNoContentMargin(true); + const auto container = box->verticalLayout(); + const auto skip = st::confcallLinkHeaderIconPadding.bottom(); box->addRow( MakeLogo(box), - st::boxRowPadding + st::confcallLinkHeaderIconPadding, + st::boxRowPadding + QMargins(0, 0, 0, skip), style::al_top); auto helper = Text::CustomEmojiHelper(); @@ -154,13 +287,91 @@ void EmojiGameStakeBox( box, tr::lng_stake_game_about(tr::rich), st::confcallLinkCenteredText), - st::boxRowPadding, + st::boxRowPadding + QMargins(0, 0, 0, skip), style::al_top )->setTryMakeSimilarLines(true); - const auto container = box->verticalLayout(); + const auto sub = st::boxRowPadding - st::defaultSubsectionTitlePadding; + const auto subtitlePadding = QMargins(sub.left(), 0, sub.right(), 0); + AddSubsectionTitle( + container, + tr::lng_stake_game_results(), + subtitlePadding); - AddSubsectionTitle(container, tr::lng_stake_game_results()); + const auto half = (st::defaultTable.border + 1) / 2; + box->addRow( + MakeTable(box, args), + st::boxRowPadding + QMargins(-half, 0, -half, skip / 2), + style::al_top); + + const auto six = Lottie::IconDescriptor{ + .name = u"dice_6"_q, + .sizeOverride = { st::emojiSize, st::emojiSize }, + }; + const auto sixText = Text::LottieEmoji(six); + const auto sixContext = Text::LottieEmojiContext(six); + const auto factory = [=]( + QStringView data, + const Text::MarkedContext &context) { + if (auto result = sixContext.customEmojiFactory(data, context)) { + return std::make_unique( + std::move(result), + 0, + true); + } + return std::unique_ptr(); + }; + const auto context = Text::MarkedContext{ + .customEmojiFactory = factory, + }; + const auto resets = box->addRow( + object_ptr( + box, + tr::lng_stake_game_resets( + lt_emoji, + rpl::single(sixText), + tr::rich), + st::confcallLinkFooterOr, + st::defaultPopupMenu, + context), + st::boxRowPadding, + style::al_top); + resets->setTryMakeSimilarLines(true); + resets->setAnimationsPausedCallback([=] { + return FlatLabel::WhichAnimationsPaused::All; + }); + + AddSubsectionTitle( + container, + tr::lng_stake_game_your(), + subtitlePadding + QMargins(0, skip, 0, sub.bottom())); + const auto field = AddTonInputField(container, { + .value = args.currentStake, + }); + box->setFocusCallback([=] { + field->setFocusFast(); + }); + + const auto submit = args.submit; + const auto callback = [=] { + const auto text = field->getLastText(); + const auto now = Ui::ParseTonAmountString(text); + if (!now) { + field->showError(); + } else { + box->closeBox(); + submit(*now); + } + }; + field->submits() | rpl::on_next(callback, field->lifetime()); + const auto button = box->addButton(rpl::single(QString()), callback); + + 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); + })); const auto close = CreateChild( container, @@ -186,39 +397,6 @@ void EmojiGameStakeBox( 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/text/text_lottie_custom_emoji.cpp b/Telegram/SourceFiles/ui/text/text_lottie_custom_emoji.cpp index e78a264c76..7791bfc2af 100644 --- a/Telegram/SourceFiles/ui/text/text_lottie_custom_emoji.cpp +++ b/Telegram/SourceFiles/ui/text/text_lottie_custom_emoji.cpp @@ -17,8 +17,8 @@ LottieCustomEmoji::LottieCustomEmoji(Lottie::IconDescriptor &&descriptor) } LottieCustomEmoji::LottieCustomEmoji( - Lottie::IconDescriptor &&descriptor, - Fn repaint) + Lottie::IconDescriptor &&descriptor, + Fn repaint) : _entityData(!descriptor.name.isEmpty() ? descriptor.name : !descriptor.path.isEmpty()