Initial stake dice game implementation.

This commit is contained in:
John Preston
2025-12-16 14:23:28 +04:00
parent a96e00e44c
commit 4a7148e20a
21 changed files with 550 additions and 104 deletions
+2
View File
@@ -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
+13
View File
@@ -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";
+2
View File
@@ -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;
+18 -7
View File
@@ -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<QString>{
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<std::vector<QString>>(
const auto list = hardcoded/*config.get<std::vector<QString>>(
"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(),
+6
View File
@@ -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;
}
}
+2
View File
@@ -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);
@@ -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,
});
@@ -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<void(const Data::DiceGameOptions &)> 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<int, 6>{};
const auto &params = 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
@@ -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<void(const Data::DiceGameOptions &)> done);
void apply(const MTPDupdateEmojiGameInfo &update);
private:
const not_null<Main::Session*> _session;
base::flat_map<QString, std::unique_ptr<DicePack>> _packs;
mtpRequestId _resolveGameOptionsRequestId = 0;
Fn<void(const Data::DiceGameOptions &)> _resolveGameOptionsCallback;
};
} // namespace Stickers
+56 -11
View File
@@ -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<HistoryView::Media> MediaTodoList::createView(
replacing);
}
MediaDice::MediaDice(not_null<HistoryItem*> parent, QString emoji, int value)
MediaDice::MediaDice(
not_null<HistoryItem*> parent,
DiceGameOutcome outcome,
QString emoji,
int value)
: Media(parent)
, _outcome(outcome)
, _emoji(emoji)
, _value(value) {
}
std::unique_ptr<Media> MediaDice::clone(not_null<HistoryItem*> parent) {
return std::make_unique<MediaDice>(parent, _emoji, _value);
return std::make_unique<MediaDice>(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<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
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<ClickHandlerContext>();
const auto weak = my.sessionWindow;
if (const auto strong = weak.get()) {
ShownToast = strong->showToast(std::move(config));
} else {
+29 -1
View File
@@ -653,14 +653,41 @@ private:
};
struct DiceGameOptions {
QByteArray seedHash;
int64 previousSteakNanoTon = 0;
std::array<int, 6> 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<HistoryItem*> parent, QString emoji, int value);
MediaDice(
not_null<HistoryItem*> parent,
DiceGameOutcome outcome,
QString emoji,
int value);
std::unique_ptr<Media> clone(not_null<HistoryItem*> 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;
@@ -366,8 +366,15 @@ std::unique_ptr<Data::Media> 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<Data::MediaDice>(
item,
outcome,
qs(media.vemoticon()),
media.vvalue().v);
}, [&](const MTPDmessageMediaStory &media) -> Result {
@@ -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<Ui::RpWidget*> AddMoneyInputIcon(
not_null<QWidget*> parent,
Ui::Text::PaletteDependentEmoji emoji) {
auto helper = Ui::Text::CustomEmojiHelper();
auto text = helper.paletteDependent(std::move(emoji));
return Ui::CreateChild<Ui::FlatLabel>(
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<Ui::NumberInput*> AddStarsInputField(
not_null<Ui::VerticalLayout*> container,
StarsInputFieldArgs &&args) {
const auto wrap = container->add(
object_ptr<Ui::FixedHeightWidget>(
container,
st::editTagField.heightMin),
st::boxRowPadding);
const auto result = Ui::CreateChild<Ui::NumberInput>(
wrap,
st::editTagField,
rpl::single(u"0"_q),
args.value ? QString::number(*args.value) : QString(),
args.max ? args.max : std::numeric_limits<int>::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<Ui::InputField*> AddTonInputField(
not_null<Ui::VerticalLayout*> container,
TonInputFieldArgs &&args) {
const auto wrap = container->add(
object_ptr<Ui::FixedHeightWidget>(
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<Ui::VerticalLayout*> container,
StarsTonPriceArgs &&args) {
@@ -51,21 +51,6 @@ void ChooseSuggestTimeBox(
not_null<Ui::GenericBox*> box,
SuggestTimeBoxArgs &&args);
struct StarsInputFieldArgs {
std::optional<int64> value;
int64 max = 0;
};
[[nodiscard]] not_null<Ui::NumberInput*> AddStarsInputField(
not_null<Ui::VerticalLayout*> container,
StarsInputFieldArgs &&args);
struct TonInputFieldArgs {
int64 value = 0;
};
[[nodiscard]] not_null<Ui::InputField*> AddTonInputField(
not_null<Ui::VerticalLayout*> container,
TonInputFieldArgs &&args);
struct StarsTonPriceInput {
Fn<void()> focusCallback;
Fn<std::optional<CreditsAmount>()> computeResult;
@@ -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();
@@ -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<Element*> parent, not_null<Data::MediaDice*> 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)) {
@@ -42,14 +42,20 @@ public:
}
}
bool updateItemData() override;
private:
void updateOutcomeMessage();
const not_null<Element*> _parent;
const not_null<Data::MediaDice*> _dice;
ClickHandlerPtr _link;
std::optional<Sticker> _start;
std::optional<Sticker> _end;
int64 _outcomeNanoTon = false;
mutable bool _showLastFrame = false;
mutable bool _drawingEnd = false;
bool _outcomeSet = false;
};
@@ -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,
@@ -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<int> reactionButtonCenterOverride() const override;
QPoint resolveCustomInfoRightBottom() const override;
bool updateItemData() override {
return _content->updateItemData();
}
void stickerClearLoopPlayed() override {
_content->stickerClearLoopPlayed();
}
@@ -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<RpWidget*> AddMoneyInputIcon(
not_null<QWidget*> parent,
Text::PaletteDependentEmoji emoji) {
auto helper = Text::CustomEmojiHelper();
auto text = helper.paletteDependent(std::move(emoji));
return CreateChild<FlatLabel>(
parent,
rpl::single(std::move(text)),
st::defaultFlatLabel,
st::defaultPopupMenu,
helper.context());
}
[[nodiscard]] object_ptr<RpWidget> MakeLogo(not_null<GenericBox*> 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<NumberInput*> AddStarsInputField(
not_null<VerticalLayout*> container,
StarsInputFieldArgs &&args) {
const auto wrap = container->add(
object_ptr<FixedHeightWidget>(
container,
st::editTagField.heightMin),
st::boxRowPadding);
const auto result = CreateChild<NumberInput>(
wrap,
st::editTagField,
rpl::single(u"0"_q),
args.value ? QString::number(*args.value) : QString(),
args.max ? args.max : std::numeric_limits<int>::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<InputField*> AddTonInputField(
not_null<VerticalLayout*> container,
TonInputFieldArgs &&args) {
const auto wrap = container->add(
object_ptr<FixedHeightWidget>(
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<GenericBox*> 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<FlatLabel>(
box,
std::move(title),
st::boxTitle,
st::defaultPopupMenu,
helper.context()),
st::boxRowPadding + st::confcallLinkTitlePadding,
style::al_top);
box->addRow(
object_ptr<FlatLabel>(
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<IconButton>(
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<FlatLabel>(
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
@@ -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<int64> value;
int64 max = 0;
};
[[nodiscard]] not_null<NumberInput*> AddStarsInputField(
not_null<VerticalLayout*> container,
StarsInputFieldArgs &&args);
struct TonInputFieldArgs {
int64 value = 0;
};
[[nodiscard]] not_null<InputField*> AddTonInputField(
not_null<VerticalLayout*> container,
TonInputFieldArgs &&args);
struct EmojiGameStakeArgs {
not_null<Main::Session*> session;
int64 currentStake = 0;
std::array<int, 6> milliRewards;
int jackpotMilliReward = 0;
Fn<void(int64)> submit;
};
void EmojiGameStakeBox(not_null<GenericBox*> box, EmojiGameStakeArgs &&args);
[[nodiscard]] inline object_ptr<GenericBox> MakeEmojiGameStakeBox(
EmojiGameStakeArgs &&args) {
return Box(EmojiGameStakeBox, std::move(args));
}
} // namespace Ui