diff --git a/Telegram/Resources/animations/dice/dice_1.tgs b/Telegram/Resources/animations/dice/dice_1.tgs deleted file mode 100644 index 36506f7108..0000000000 Binary files a/Telegram/Resources/animations/dice/dice_1.tgs and /dev/null differ diff --git a/Telegram/Resources/animations/dice/dice_2.tgs b/Telegram/Resources/animations/dice/dice_2.tgs deleted file mode 100644 index ac0de6ff5a..0000000000 Binary files a/Telegram/Resources/animations/dice/dice_2.tgs and /dev/null differ diff --git a/Telegram/Resources/animations/dice/dice_3.tgs b/Telegram/Resources/animations/dice/dice_3.tgs deleted file mode 100644 index 1362b087fe..0000000000 Binary files a/Telegram/Resources/animations/dice/dice_3.tgs and /dev/null differ diff --git a/Telegram/Resources/animations/dice/dice_4.tgs b/Telegram/Resources/animations/dice/dice_4.tgs deleted file mode 100644 index 60b7c6d8ce..0000000000 Binary files a/Telegram/Resources/animations/dice/dice_4.tgs and /dev/null differ diff --git a/Telegram/Resources/animations/dice/dice_5.tgs b/Telegram/Resources/animations/dice/dice_5.tgs deleted file mode 100644 index 77bb708982..0000000000 Binary files a/Telegram/Resources/animations/dice/dice_5.tgs and /dev/null differ diff --git a/Telegram/Resources/art/dice/dice1.svg b/Telegram/Resources/art/dice/dice1.svg new file mode 100644 index 0000000000..4ab6abbe61 --- /dev/null +++ b/Telegram/Resources/art/dice/dice1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/art/dice/dice2.svg b/Telegram/Resources/art/dice/dice2.svg new file mode 100644 index 0000000000..1e9e0c8534 --- /dev/null +++ b/Telegram/Resources/art/dice/dice2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/art/dice/dice3.svg b/Telegram/Resources/art/dice/dice3.svg new file mode 100644 index 0000000000..84ff3fcc13 --- /dev/null +++ b/Telegram/Resources/art/dice/dice3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/art/dice/dice4.svg b/Telegram/Resources/art/dice/dice4.svg new file mode 100644 index 0000000000..dc4af3c88f --- /dev/null +++ b/Telegram/Resources/art/dice/dice4.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/art/dice/dice5.svg b/Telegram/Resources/art/dice/dice5.svg new file mode 100644 index 0000000000..cdd1efd1db --- /dev/null +++ b/Telegram/Resources/art/dice/dice5.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/art/dice/dice6.svg b/Telegram/Resources/art/dice/dice6.svg new file mode 100644 index 0000000000..f024e88b05 --- /dev/null +++ b/Telegram/Resources/art/dice/dice6.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/mini_roll.svg b/Telegram/Resources/icons/chat/mini_roll.svg new file mode 100644 index 0000000000..158f3d10b8 --- /dev/null +++ b/Telegram/Resources/icons/chat/mini_roll.svg @@ -0,0 +1,7 @@ + + + Mini / mini_roll + + + + \ No newline at end of file diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc index 6c7e4c94c7..f448e9e4a5 100644 --- a/Telegram/Resources/qrc/telegram/animations.qrc +++ b/Telegram/Resources/qrc/telegram/animations.qrc @@ -66,12 +66,6 @@ ../../animations/dice/slot_back.tgs ../../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 diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc index e439a4e012..ca37cc179a 100644 --- a/Telegram/Resources/qrc/telegram/telegram.qrc +++ b/Telegram/Resources/qrc/telegram/telegram.qrc @@ -36,6 +36,12 @@ ../../art/topic_icons/general.svg ../../icons/info/edit/links_subscription.svg ../../icons/plane_white.svg + ../../art/dice/dice1.svg + ../../art/dice/dice2.svg + ../../art/dice/dice3.svg + ../../art/dice/dice4.svg + ../../art/dice/dice5.svg + ../../art/dice/dice6.svg ../../icons/calls/hands.lottie 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 72279f5964..71c0d033e5 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.cpp @@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/controls/history_view_suggest_options.h" -#include "base/event_filter.h" #include "base/unixtime.h" #include "boxes/star_gift_box.h" #include "chat_helpers/compose/compose_show.h" @@ -21,7 +20,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "history/history_item_components.h" #include "history/history_item_helpers.h" -#include "info/channel_statistics/earn/earn_format.h" #include "info/channel_statistics/earn/earn_icons.h" #include "lang/lang_keys.h" #include "lottie/lottie_icon.h" @@ -111,36 +109,6 @@ void ChooseSuggestTimeBox( }); } -void AddApproximateUsd( - not_null field, - not_null session, - rpl::producer price) { - auto value = std::move(price) | rpl::map([=](CreditsAmount amount) { - if (!amount) { - return QString(); - } - const auto appConfig = &session->appConfig(); - const auto rate = amount.ton() - ? appConfig->currencySellRate() - : (appConfig->starsSellRate() / 100.); - return Info::ChannelEarn::ToUsd(amount, rate, 2); - }); - const auto usd = Ui::CreateChild( - field, - std::move(value), - st::suggestPriceEstimate); - const auto move = [=] { - usd->moveToRight(0, st::suggestPriceEstimateTop); - }; - base::install_event_filter(field, [=](not_null e) { - if (e->type() == QEvent::Resize) { - move(); - } - return base::EventFilterResult::Continue; - }); - usd->widthValue() | rpl::on_next(move, usd->lifetime()); -} - StarsTonPriceInput AddStarsTonPriceInput( not_null container, StarsTonPriceArgs &&args) { diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index 63c70efb24..1dcb93feb4 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/message_bubble.h" #include "ui/chat/chat_style.h" #include "ui/effects/reaction_fly_animation.h" +#include "ui/text/custom_emoji_helper.h" #include "ui/text/format_values.h" #include "ui/text/text_options.h" #include "ui/text/text_utilities.h" @@ -473,6 +474,8 @@ void BottomInfo::layoutDateText() { : name.isEmpty() ? date : (name + afterAuthor); + auto helper = Ui::Text::CustomEmojiHelper( + Core::TextContext({ .session = &_reactionsOwner->session() })); auto marked = TextWithEntities(); if (const auto count = _data.stars) { marked.append( @@ -480,16 +483,24 @@ void BottomInfo::layoutDateText() { ).append(Lang::FormatCountToShort(count).string).append(u", "_q); } if (const auto stake = _data.tonStake) { - marked.append(QString::number(stake / 1e9) - + QString::fromUtf8("\xf0\x9f\x92\x8e") - + " "); + marked.append( + QString::number(stake / 1e9) + ).append(helper.image({ + .image = Ui::Emoji::SinglePixmap( + Ui::Emoji::Find(QString::fromUtf8("\xf0\x9f\x92\x8e")), + Ui::Emoji::GetSizeNormal()).toImage().scaledToHeight( + st::stakeIconEmojiSize * style::DevicePixelRatio(), + Qt::SmoothTransformation), + .margin = QMargins(0, st::stakeIconEmojiTop, 0, 0), + .textColor = false, + })).append(" "); } marked.append(full); _authorEditedDate.setMarkedText( st::msgDateTextStyle, marked, Ui::NameTextOptions(), - Core::TextContext({ .session = &_reactionsOwner->session() })); + helper.context()); } void BottomInfo::layoutViewsText() { diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index 944538aba4..469cb33e50 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -280,6 +280,27 @@ bool AppConfig::settingsDisplayPasskeys() const { return get(u"settings_display_passkeys"_q, false); } +int64 AppConfig::stakeDiceNanoTonMin() const { + return get(u"ton_stakedice_stake_amount_min"_q, 100'000'000LL); +} + +int64 AppConfig::stakeDiceNanoTonMax() const { + return get(u"ton_stakedice_stake_amount_max"_q, 50'000'000'000LL); +} + +std::vector AppConfig::stakeDiceNanoTonSuggested() const { + return get>( + u"ton_stakedice_stake_suggested_amounts"_q, + std::vector{ + 100'000'000LL, + 1'000'000'000LL, + 2'000'000'000LL, + 5'000'000'000LL, + 10'000'000'000LL, + 20'000'000'000LL, + }); +} + void AppConfig::refresh(bool force) { if (_requestId || !_api) { if (force) { @@ -464,6 +485,27 @@ std::vector AppConfig::getIntArray( }); } +std::vector AppConfig::getInt64Array( + const QString &key, + std::vector &&fallback) const { + return getValue(key, [&](const MTPJSONValue &value) { + return value.match([&](const MTPDjsonArray &data) { + auto result = std::vector(); + result.reserve(data.vvalue().v.size()); + for (const auto &entry : data.vvalue().v) { + if (entry.type() != mtpc_jsonNumber) { + return std::move(fallback); + } + result.push_back( + int64(base::SafeRound(entry.c_jsonNumber().vvalue().v))); + } + return result; + }, [&](const auto &data) { + return std::move(fallback); + }); + }); +} + bool AppConfig::newRequirePremiumFree() const { return get( u"new_noncontact_peers_require_premium_without_ownpremium"_q, diff --git a/Telegram/SourceFiles/main/main_app_config.h b/Telegram/SourceFiles/main/main_app_config.h index 500d8f57f6..5e9cc97659 100644 --- a/Telegram/SourceFiles/main/main_app_config.h +++ b/Telegram/SourceFiles/main/main_app_config.h @@ -47,6 +47,8 @@ public: return getStringMap(key, std::move(fallback)); } else if constexpr (std::is_same_v>) { return getIntArray(key, std::move(fallback)); + } else if constexpr (std::is_same_v>) { + return getInt64Array(key, std::move(fallback)); } else if constexpr (std::is_same_v) { return getBool(key, fallback); } @@ -128,6 +130,10 @@ public: [[nodiscard]] int passkeysAccountPasskeysMax() const; [[nodiscard]] bool settingsDisplayPasskeys() const; + [[nodiscard]] int64 stakeDiceNanoTonMin() const; + [[nodiscard]] int64 stakeDiceNanoTonMax() const; + [[nodiscard]] std::vector stakeDiceNanoTonSuggested() const; + using StarsColoring = Calls::Group::Ui::StarsColoring; [[nodiscard]] std::vector groupCallColorings() const; @@ -159,6 +165,9 @@ private: [[nodiscard]] std::vector getIntArray( const QString &key, std::vector &&fallback) const; + [[nodiscard]] std::vector getInt64Array( + const QString &key, + std::vector &&fallback) const; void updateIgnoredRestrictionReasons(std::vector was); diff --git a/Telegram/SourceFiles/ui/boxes/emoji_stake_box.cpp b/Telegram/SourceFiles/ui/boxes/emoji_stake_box.cpp index 7f64486d3a..8db9e85fab 100644 --- a/Telegram/SourceFiles/ui/boxes/emoji_stake_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/emoji_stake_box.cpp @@ -7,15 +7,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/boxes/emoji_stake_box.h" +#include "base/event_filter.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_format.h" #include "info/channel_statistics/earn/earn_icons.h" #include "lang/lang_keys.h" #include "lottie/lottie_icon.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "settings/settings_common.h" // CreateLottieIcon #include "settings/settings_credits_graphics.h" // AddBalanceWidget @@ -39,6 +42,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_settings.h" // settingsCloudPasswordIconSize #include "styles/style_widgets.h" +#include + namespace Ui { namespace { @@ -55,12 +60,30 @@ namespace { helper.context()); } +[[nodiscard]] QImage MakeEmojiFrame(int index, int size) { + const auto path = u":/gui/dice/dice%1.svg"_q.arg(index); + + const auto ratio = style::DevicePixelRatio(); + auto svg = QSvgRenderer(path); + auto result = QImage( + QSize(size, size) * ratio, + QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(ratio); + result.fill(Qt::transparent); + + auto p = QPainter(&result); + svg.render(&p, QRect(0, 0, size, size)); + p.end(); + + return result; +} + [[nodiscard]] object_ptr MakeLogo(not_null box) { const auto &size = st::settingsCloudPasswordIconSize; auto icon = Settings::CreateLottieIcon( box->verticalLayout(), { .name = u"dice_6"_q, .sizeOverride = { size, size } }, - st::settingLocalPasscodeIconPadding); + QMargins()); const auto animate = std::move(icon.animate); box->showFinishes() | rpl::take(1) | rpl::on_next([=] { animate(anim::repeat::once); @@ -75,19 +98,17 @@ namespace { const auto raw = result.data(); struct State { - std::array, 6> dices; + std::array 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; + const auto esize = st::stakeEmojiSize; 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(); }); + state->dices[i] = MakeEmojiFrame(i + 1, esize); + const auto value = (i < args.milliRewards.size()) ? args.milliRewards[i] : 0; @@ -137,13 +158,12 @@ namespace { auto hq = PainterHighQualityEnabler(p); p.drawPath(path); - const auto etop = -style::ConvertScale(10); + const auto etop = st::stakeEmojiTop; 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); @@ -157,25 +177,17 @@ namespace { 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 } - }); + p.drawImage( + x + (w - esize) / 2, + y + etop, + state->dices[i]); } else { - const auto e = state->dices.back().get(); + const auto &last = state->dices.back(); 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.drawImage( + x + (w - 3 * esize) / 2 + (esize * j), + y + etop, + last); } } @@ -249,6 +261,36 @@ not_null AddTonInputField( return result; } +void AddApproximateUsd( + not_null field, + not_null session, + rpl::producer price) { + auto value = std::move(price) | rpl::map([=](CreditsAmount amount) { + if (!amount) { + return QString(); + } + const auto appConfig = &session->appConfig(); + const auto rate = amount.ton() + ? appConfig->currencySellRate() + : (appConfig->starsSellRate() / 100.); + return Info::ChannelEarn::ToUsd(amount, rate, 2); + }); + const auto usd = Ui::CreateChild( + field, + std::move(value), + st::suggestPriceEstimate); + const auto move = [=] { + usd->moveToRight(0, st::suggestPriceEstimateTop); + }; + base::install_event_filter(field, [=](not_null e) { + if (e->type() == QEvent::Resize) { + move(); + } + return base::EventFilterResult::Continue; + }); + usd->widthValue() | rpl::on_next(move, usd->lifetime()); +} + void EmojiGameStakeBox( not_null box, EmojiGameStakeArgs &&args) { @@ -268,6 +310,10 @@ void EmojiGameStakeBox( Text::CustomEmojiTextBadge( tr::lng_stake_game_beta(tr::now), st::customEmojiTextBadge)); + const auto sixText = helper.image({ + .image = MakeEmojiFrame(6, st::emojiSize), + .textColor = false, + }); auto title = tr::lng_stake_game_title( tr::marked ) | rpl::map([=](TextWithEntities &&text) { @@ -304,12 +350,7 @@ void EmojiGameStakeBox( 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 sixContext = helper.context(); const auto factory = [=]( QStringView data, const Text::MarkedContext &context) { @@ -352,15 +393,35 @@ void EmojiGameStakeBox( field->setFocusFast(); }); + const auto price = field->lifetime().make_state>( + args.currentStake); + auto priceValue = price->value() | rpl::map([=](int64 amount) { + return CreditsAmount( + amount / Ui::kNanosInOne, + amount % Ui::kNanosInOne, + CreditsType::Ton); + }); + AddApproximateUsd( + field, + args.session, + std::move(priceValue)); + + field->changes() | rpl::on_next([=] { + *price = Ui::ParseTonAmountString(field->getLastText()).value_or(0); + }, field->lifetime()); + const auto submit = args.submit; const auto callback = [=] { const auto text = field->getLastText(); - const auto now = Ui::ParseTonAmountString(text); - if (!now) { + const auto now = Ui::ParseTonAmountString(text).value_or(0); + const auto appConfig = &args.session->appConfig(); + const auto min = appConfig->stakeDiceNanoTonMin(); + const auto max = appConfig->stakeDiceNanoTonMax(); + if (!now || now < min || now > max) { field->showError(); } else { box->closeBox(); - submit(*now); + submit(now); } }; field->submits() | rpl::on_next(callback, field->lifetime()); @@ -368,7 +429,8 @@ void EmojiGameStakeBox( button->setText(tr::lng_stake_game_save_and_roll( ) | rpl::map([=](QString text) { - return tr::marked( + return Text::IconEmoji( + &st::stakeEmojiIcon, QString::fromUtf8("\xf0\x9f\x8e\xb2") ).append(' ').append(text); })); diff --git a/Telegram/SourceFiles/ui/boxes/emoji_stake_box.h b/Telegram/SourceFiles/ui/boxes/emoji_stake_box.h index 3b5133582b..3a84abf8ed 100644 --- a/Telegram/SourceFiles/ui/boxes/emoji_stake_box.h +++ b/Telegram/SourceFiles/ui/boxes/emoji_stake_box.h @@ -34,6 +34,11 @@ struct TonInputFieldArgs { not_null container, TonInputFieldArgs &&args); +void AddApproximateUsd( + not_null field, + not_null session, + rpl::producer price); + struct EmojiGameStakeArgs { not_null session; int64 currentStake = 0; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 8571434c27..2da01898ef 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1459,3 +1459,12 @@ messageGiftIconSkip: 30px; newChatIcon: icon{{ "chat/new_topic", windowFgActive }}; newChatIconPadding: margins(2px, 2px, 2px, 2px); + +stakeEmojiSize: 20px; +stakeEmojiTop: 8px; +stakeEmojiIcon: IconEmoji { + icon: icon{{ "chat/mini_roll-18x18", activeButtonFg }}; + padding: margins(1px, 2px, 1px, 0px); +} +stakeIconEmojiSize: 12px; +stakeIconEmojiTop: 3px;