ZaStoGram_desktop/Telegram/SourceFiles/wallet/wallet_ton_connect_request_box.cpp
John Preston 68d1365911 Paint the TON Connect card balances like the Money card
Task: 2026/10/08/paint-the-ton-connect-card-balance-like-the-money-card
2026-10-08 22:42:48 +04:00

1032 lines
28 KiB
C++

/*
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 "wallet/wallet_ton_connect_request_box.h"
#include "lang/lang_keys.h"
#include "ui/controls/button_busy.h"
#include "ui/controls/table_rows.h"
#include "ui/controls/ton_common.h"
#include "ui/layers/generic_box.h"
#include "ui/text/custom_emoji_helper.h"
#include "ui/text/text.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/table_layout.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/new_badges.h"
#include "ui/ui_utility.h"
#include "ui/vertical_list.h"
#include "wallet/wallet_address.h"
#include "wallet/wallet_amount_painter.h"
#include "wallet/wallet_content.h"
#include "wallet/wallet_fiat.h"
#include "wallet/wallet_ton_connect.h"
#include "wallet/wallet_ton_connect_box.h"
#include "wallet/wallet_ton_connect_emulation.h"
#include "styles/style_giveaway.h"
#include "styles/style_layers.h"
#include "styles/style_wallet.h"
namespace Wallet {
namespace {
using Phase = TonConnectRequestPhase;
using ActionKind = TonConnectActionKind;
using ActionSide = TonConnectActionSide;
using EmulationStatus = TonConnectEmulationStatus;
using SignDataType = TonConnectSignDataType;
constexpr auto kPayloadShown = 1024;
constexpr auto kFieldDepthShown = 8;
constexpr auto kMinus = QChar(0x2212);
struct State {
std::optional<TonConnectRequestBoxState> built;
Fn<void(const TonConnectHeaderState &, anim::type)> header;
rpl::variable<std::optional<int64>> feeNano;
QPointer<Ui::VerticalLayout> body;
QPointer<Ui::IconButton> back;
QPointer<Ui::RoundButton> confirm;
QPointer<Ui::RoundButton> decline;
QPointer<Ui::RoundButton> cancel;
QPointer<Ui::RoundButton> unlock;
QPointer<Ui::SlideWrap<Ui::FlatLabel>> fee;
QPointer<Ui::SlideWrap<Ui::FlatLabel>> error;
std::shared_ptr<bool> markPlayed = std::make_shared<bool>();
bool details = false;
};
struct Context {
not_null<Main::Session*> session;
Fn<void()> unlock;
Fn<void()> restore;
Fn<void()> confirm;
Fn<void()> decline;
};
struct Destinations {
QString first;
int distinct = 0;
};
[[nodiscard]] bool SameStructure(
const TonConnectRequestBoxState &built,
const TonConnectRequestBoxState &now) {
auto aligned = now;
aligned.feeNano = built.feeNano;
aligned.error = built.error;
aligned.feeLoading = built.feeLoading;
aligned.confirmable = built.confirmable;
aligned.busy = built.busy;
aligned.declining = built.declining;
return (aligned == built);
}
[[nodiscard]] bool Busy(not_null<State*> state) {
return state->built && state->built->busy;
}
[[nodiscard]] bool Confirmable(not_null<State*> state) {
return state->built && state->built->confirmable;
}
[[nodiscard]] QString PayloadShown(const QString &payload) {
return (payload.size() <= kPayloadShown)
? payload
: (payload.left(kPayloadShown) + Ui::kQEllipsis);
}
[[nodiscard]] QString DisplayAddress(const QString &destination) {
const auto parsed = ParseAddress(destination);
if (!parsed || parsed->friendly) {
return destination;
}
const auto friendly = FormatFriendly(
parsed->raw,
false,
parsed->testnet);
return friendly.isEmpty() ? destination : friendly;
}
[[nodiscard]] Destinations CollectDestinations(
const TonConnectTransfer &transfer) {
auto keys = base::flat_set<QString>();
for (const auto &message : transfer.messages) {
const auto canonical = CanonicalAddress(message.destination);
keys.emplace(canonical.isEmpty() ? message.destination : canonical);
}
return {
.first = (transfer.messages.empty()
? QString()
: DisplayAddress(transfer.messages.front().destination)),
.distinct = int(keys.size()),
};
}
[[nodiscard]] QString OutgoingGram(int64 nano) {
return kMinus + Ui::FormatTonAmount(nano).full + ' ' + GramTicker();
}
[[nodiscard]] const TonConnectEmulation *ShownEmulation(
const TonConnectRequestBoxState &now) {
const auto emulation = now.emulation.get();
return (now.transfer
&& emulation
&& emulation->status == EmulationStatus::Shown)
? emulation
: nullptr;
}
[[nodiscard]] QString ActionTitle(ActionKind kind) {
switch (kind) {
case ActionKind::Withdraw:
return tr::lng_wallet_row_withdrawal(tr::now);
case ActionKind::Deposit:
return tr::lng_wallet_row_deposit(tr::now);
case ActionKind::Transfer:
return tr::lng_wallet_connect_request_transfer(tr::now);
case ActionKind::Excess:
return tr::lng_wallet_connect_request_excess(tr::now);
case ActionKind::CallContract:
return tr::lng_wallet_connect_request_call_contract(tr::now);
case ActionKind::DeployContract:
return tr::lng_wallet_connect_request_deploy_contract(tr::now);
case ActionKind::Unknown:
return tr::lng_wallet_connect_request_unknown_operation(tr::now);
}
Unexpected("Kind in TonConnectRequestBox ActionTitle.");
}
[[nodiscard]] ActionRowIcon ActionIcon(ActionKind kind) {
switch (kind) {
case ActionKind::Withdraw:
case ActionKind::Transfer:
return ActionRowIcon::Outgoing;
case ActionKind::Deposit:
case ActionKind::Excess:
return ActionRowIcon::Incoming;
case ActionKind::CallContract:
case ActionKind::DeployContract:
case ActionKind::Unknown:
return ActionRowIcon::Gear;
}
Unexpected("Kind in TonConnectRequestBox ActionIcon.");
}
[[nodiscard]] ActionRowSign ActionSign(ActionSide side) {
switch (side) {
case ActionSide::None: return ActionRowSign::None;
case ActionSide::Incoming: return ActionRowSign::Plus;
case ActionSide::Outgoing: return ActionRowSign::Minus;
}
Unexpected("Side in TonConnectRequestBox ActionSign.");
}
[[nodiscard]] QString ActionAddress(
const QString &raw,
const TonConnectTransfer &transfer) {
if (raw.isEmpty()) {
return QString();
}
for (const auto &message : transfer.messages) {
if (CanonicalAddress(message.destination) == raw) {
return DisplayAddress(message.destination);
}
}
return FormatFriendly(raw, true);
}
[[nodiscard]] ActionRowArgs ActionRowFor(
const TonConnectAction &action,
const TonConnectTransfer &transfer) {
return {
.kind = ActionTitle(action.kind),
.address = ActionAddress(action.counterparty, transfer),
.amountNano = action.amountNano,
.sign = ActionSign(action.side),
.icon = ActionIcon(action.kind),
};
}
[[nodiscard]] bool HasContent(const TonConnectMessage &message) {
return !message.comment.isEmpty()
|| !message.payload.isEmpty()
|| message.deploys;
}
[[nodiscard]] QString HeaderTitle(const TonConnectRequestBoxState &now) {
switch (now.phase) {
case Phase::Loading:
return tr::lng_contacts_loading(tr::now);
case Phase::Locked:
case Phase::Restore:
return now.topic;
case Phase::Confirm:
if (now.signData) {
return tr::lng_wallet_connect_sign_title(tr::now);
}
return now.name.isEmpty()
? tr::lng_wallet_connect_request_title(tr::now)
: tr::lng_wallet_connect_request_title_app(
tr::now,
lt_name,
now.name);
case Phase::Notice:
case Phase::Unhandled:
return now.name.isEmpty()
? tr::lng_wallet_connect_request_title(tr::now)
: now.name;
}
Unexpected("Phase in TonConnectRequestBox HeaderTitle.");
}
[[nodiscard]] TonConnectHeaderState HeaderStateFor(
const TonConnectRequestBoxState &now) {
return {
.title = HeaderTitle(now),
.domain = now.domain,
.icon = now.icon,
.loading = (now.phase == Phase::Loading),
};
}
[[nodiscard]] rpl::producer<QString> FeeText(
not_null<State*> state,
not_null<Main::Session*> session) {
return rpl::combine(
state->feeNano.value(),
FiatRateValue(session)
) | rpl::map([](std::optional<int64> fee, const FiatRate &rate) {
return fee
? tr::lng_wallet_backup_update_fee(
tr::now,
lt_amount,
Ui::FormatTonAmount(*fee).full,
lt_fiat,
FormatFiat(*fee, rate, kFeeFiatDecimals, true))
: tr::lng_wallet_connect_request_fee_loading(tr::now);
});
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakeTotalValue(
not_null<QWidget*> parent,
not_null<Main::Session*> session,
int64 total) {
auto text = FiatRateValue(
session
) | rpl::map([=](const FiatRate &rate) {
auto result = tr::marked(OutgoingGram(total));
result.append(QChar(' '));
result.append(Ui::Text::Colorized(
FormatFiat(total, rate, kFiatCurrencyDecimals, true)));
return result;
});
return object_ptr<Ui::FlatLabel>(
parent,
std::move(text),
st::walletTonConnectAmountLabel);
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakeMessageValue(
not_null<QWidget*> parent,
std::shared_ptr<Ui::Show> show,
const TonConnectMessage &message) {
auto result = object_ptr<Ui::VerticalLayout>(parent);
const auto cell = result.data();
cell->add(object_ptr<Ui::FlatLabel>(
cell,
OutgoingGram(message.amountNano),
st::walletTonConnectAmountLabel));
Ui::AddSkip(cell, st::walletRowSkip);
cell->add(AddressValueLabel(
cell,
std::move(show),
DisplayAddress(message.destination)));
if (!message.comment.isEmpty()) {
Ui::AddSkip(cell, st::walletRowSkip);
cell->add(MakeCommentBubble(
cell,
object_ptr<Ui::FlatLabel>(
cell,
message.comment,
st::walletCommentLabel),
st::windowBgOver));
} else if (!message.payload.isEmpty()) {
Ui::AddSkip(cell, st::walletRowSkip);
cell->add(object_ptr<Ui::FlatLabel>(
cell,
tr::lng_wallet_connect_request_payload(),
st::walletTonConnectNoteLabel));
Ui::AddSkip(cell, st::walletRowSkip);
auto payload = object_ptr<Ui::FlatLabel>(
cell,
rpl::single(Ui::Text::Wrapped(
tr::marked(PayloadShown(message.payload)),
EntityType::Code)),
st::walletTonConnectPayloadLabel);
payload->setSelectable(true);
cell->add(MakeCommentBubble(
cell,
std::move(payload),
st::windowBgOver));
}
if (message.deploys) {
Ui::AddSkip(cell, st::walletRowSkip);
cell->add(object_ptr<Ui::FlatLabel>(
cell,
tr::lng_wallet_connect_request_deploy(),
st::walletTonConnectNoteLabel));
}
return result;
}
void Update(
not_null<State*> state,
const TonConnectRequestBoxState &now,
anim::type animated) {
const auto confirming = now.busy && !now.declining;
Ui::SetButtonBusy(state->confirm.data(), confirming);
Ui::SetButtonDimmed(
state->confirm.data(),
confirming || !now.confirmable);
Ui::SetButtonBusy(state->decline.data(), now.busy && now.declining);
Ui::SetButtonDimmed(state->cancel.data(), now.busy);
Ui::SetButtonBusy(state->unlock.data(), now.busy);
state->feeNano = now.feeNano;
if (const auto fee = state->fee.data()) {
fee->toggle(now.feeNano || now.feeLoading, animated);
}
if (const auto error = state->error.data()) {
if (!now.error.isEmpty()) {
error->entity()->setText(now.error);
}
error->toggle(!now.error.isEmpty(), animated);
}
}
void FillLoading(not_null<Ui::GenericBox*> box) {
const auto cancel = SetTonConnectButtons(
box,
tr::lng_cancel(),
nullptr).secondary;
cancel->setClickedCallback([=] { box->closeBox(); });
}
void FillKeyNeeded(
not_null<Ui::GenericBox*> box,
not_null<State*> state,
rpl::producer<QString> text,
rpl::producer<QString> primary,
Fn<void()> pressed) {
const auto body = state->body.data();
body->add(
object_ptr<Ui::FlatLabel>(
body,
std::move(text),
st::walletConnectTextLabel),
st::walletConnectTextMargin,
style::al_top
)->setTryMakeSimilarLines(true);
const auto buttons = SetTonConnectButtons(
box,
tr::lng_cancel(),
std::move(primary));
buttons.secondary->setClickedCallback([=] { box->closeBox(); });
buttons.primary->setClickedCallback([=] {
if (!Busy(state)) {
pressed();
}
});
state->unlock = buttons.primary;
}
void FillNotice(
not_null<Ui::GenericBox*> box,
not_null<State*> state,
const TonConnectRequestBoxState &now) {
const auto body = state->body.data();
body->add(
object_ptr<Ui::FlatLabel>(
body,
now.notice,
st::walletConnectTextLabel),
st::walletConnectTextMargin,
style::al_top);
const auto close = SetTonConnectButtons(
box,
nullptr,
tr::lng_close()).primary;
close->setClickedCallback([=] { box->closeBox(); });
}
void AddErrorLine(not_null<State*> state) {
const auto body = state->body.data();
state->error = body->add(
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
body,
object_ptr<Ui::FlatLabel>(body, st::walletConnectErrorLabel),
style::margins(0, st::defaultVerticalListSkip, 0, 0)),
st::boxRowPadding,
style::al_top);
}
void FillUnhandled(
not_null<Ui::GenericBox*> box,
not_null<State*> state,
const Context &context,
const TonConnectRequestBoxState &now) {
const auto body = state->body.data();
body->add(
object_ptr<Ui::FlatLabel>(
body,
now.notice,
st::walletConnectTextLabel),
st::walletConnectTextMargin,
style::al_top
)->setTryMakeSimilarLines(true);
body->add(
object_ptr<Ui::FlatLabel>(
body,
tr::lng_wallet_connect_request_unhandled_about(),
st::walletConnectCaptionLabel),
st::walletConnectCaptionMargin,
style::al_top
)->setTryMakeSimilarLines(true);
AddErrorLine(state);
const auto buttons = SetTonConnectButtons(
box,
tr::lng_cancel(),
tr::lng_wallet_connect_request_decline());
buttons.secondary->setClickedCallback([=] {
if (!Busy(state)) {
box->closeBox();
}
});
buttons.primary->setClickedCallback([=, decline = context.decline] {
if (!Busy(state)) {
decline();
}
});
state->cancel = buttons.secondary;
state->decline = buttons.primary;
}
void AddTraceWarning(
not_null<Ui::VerticalLayout*> body,
const TonConnectRequestBoxState &now) {
const auto emulation = now.emulation.get();
if (!emulation
|| (emulation->status != EmulationStatus::Failed
&& emulation->status != EmulationStatus::Incomplete)) {
return;
}
const auto failed = (emulation->status == EmulationStatus::Failed);
body->add(
object_ptr<Ui::FlatLabel>(
body,
(failed
? tr::lng_wallet_connect_request_trace_failed()
: tr::lng_wallet_connect_request_trace_incomplete()),
(failed
? st::walletConnectErrorLabel
: st::walletConnectCaptionLabel)),
st::walletConnectCaptionMargin,
style::al_top
)->setTryMakeSimilarLines(true);
}
void AddFeeLine(
not_null<State*> state,
const Context &context,
const TonConnectRequestBoxState &now) {
const auto body = state->body.data();
state->fee = body->add(
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
body,
object_ptr<Ui::FlatLabel>(
body,
FeeText(state, context.session),
st::walletConnectCaptionLabel),
st::walletConnectCaptionMargin),
style::margins(),
style::al_top);
state->fee->entity()->setTryMakeSimilarLines(true);
AddTraceWarning(body, now);
}
void AddDecisionButtons(
not_null<Ui::GenericBox*> box,
not_null<State*> state,
const Context &context,
rpl::producer<QString> secondary,
rpl::producer<QString> primary) {
AddErrorLine(state);
const auto buttons = SetTonConnectButtons(
box,
std::move(secondary),
std::move(primary));
buttons.secondary->setClickedCallback([=, decline = context.decline] {
if (!Busy(state)) {
decline();
}
});
buttons.primary->setClickedCallback([=, confirm = context.confirm] {
if (!Busy(state) && Confirmable(state)) {
confirm();
}
});
state->decline = buttons.secondary;
state->confirm = buttons.primary;
}
void AddConfirmTail(
not_null<Ui::GenericBox*> box,
not_null<State*> state,
const Context &context,
const TonConnectRequestBoxState &now) {
AddFeeLine(state, context, now);
AddDecisionButtons(
box,
state,
context,
tr::lng_wallet_connect_request_decline(),
tr::lng_wallet_connect_request_confirm());
}
void SetDetails(
not_null<Ui::GenericBox*> box,
not_null<State*> state,
const Context &context,
bool details);
void FillSheet(
not_null<Ui::GenericBox*> box,
not_null<State*> state,
const Context &context,
const TonConnectRequestBoxState &now) {
const auto body = state->body.data();
const auto destinations = now.transfer
? CollectDestinations(*now.transfer)
: Destinations();
const auto shown = ShownEmulation(now);
body->add(
MakeTransferCard(body, context.session, {
.totalNano = now.transfer ? now.transfer->totalNano : int64(0),
.netNano = (shown
? std::make_optional(shown->netNano)
: std::nullopt),
.destination = ((destinations.distinct == 1)
? destinations.first
: QString()),
.recipients = ((destinations.distinct >= 2)
? destinations.distinct
: 0),
.info = [=] {
// The rebuild destroys the card this click is still inside.
Ui::PostponeCall(box, [=] {
SetDetails(box, state, context, true);
});
},
.markPlayed = state->markPlayed,
}),
st::walletConnectCardMargin,
style::al_top);
AddConfirmTail(box, state, context, now);
}
[[nodiscard]] style::margins DetailsTitlePadding() {
return style::margins(
(st::boxRowPadding.left()
- st::defaultSubsectionTitlePadding.left()),
0,
0,
0);
}
void FillRequestDetails(
not_null<Ui::VerticalLayout*> body,
std::shared_ptr<Ui::Show> show,
const Context &context,
const TonConnectTransfer &transfer) {
const auto padding = DetailsTitlePadding();
Ui::AddSubsectionTitle(
body,
tr::lng_wallet_connect_request_preview(),
padding);
const auto preview = AddDetailsTableFrame(body);
Ui::AddTableRow(
preview,
tr::lng_wallet_connect_request_total(),
MakeTotalValue(preview, context.session, transfer.totalNano));
const auto destinations = CollectDestinations(transfer);
if (destinations.distinct == 1) {
Ui::AddTableRow(
preview,
tr::lng_wallet_details_recipient(),
AddressValueLabel(preview, show, destinations.first));
} else if (destinations.distinct >= 2) {
Ui::AddTableRow(
preview,
tr::lng_wallet_connect_request_recipients_title(),
rpl::single(tr::marked(
QString::number(destinations.distinct))));
}
Ui::AddSubsectionTitle(
body,
tr::lng_wallet_connect_request_actions(),
padding);
const auto list = AddDetailsTableFrame(body);
for (const auto &message : transfer.messages) {
Ui::AddTableRow(
list,
tr::lng_wallet_connect_request_transfer(),
MakeMessageValue(list, show, message));
}
}
void FillEmulatedDetails(
not_null<Ui::VerticalLayout*> body,
std::shared_ptr<Ui::Show> show,
const TonConnectTransfer &transfer,
const TonConnectEmulation &emulation) {
const auto padding = DetailsTitlePadding();
Ui::AddSubsectionTitle(
body,
tr::lng_wallet_connect_request_preview(),
padding);
const auto table = AddDetailsTableFrame(body);
for (const auto &action : emulation.actions) {
table->addRow(
nullptr,
MakeActionRow(table, ActionRowFor(action, transfer)),
style::margins(),
style::margins());
}
const auto &messages = transfer.messages;
if (ranges::none_of(messages, HasContent)) {
return;
}
Ui::AddSubsectionTitle(
body,
tr::lng_wallet_connect_request_messages(),
padding);
const auto list = AddDetailsTableFrame(body);
for (const auto &message : messages) {
if (HasContent(message)) {
Ui::AddTableRow(
list,
tr::lng_wallet_connect_request_message(),
MakeMessageValue(list, show, message));
}
}
}
void AddDetailsSkip(not_null<Ui::VerticalLayout*> body) {
Ui::AddSkip(
body,
(st::walletConnectCardMargin.bottom()
- st::giveawayGiftCodeTableMargin.bottom()));
}
[[nodiscard]] object_ptr<Ui::FlatLabel> MakeWarningLabel(
not_null<QWidget*> parent,
TextWithEntities text,
const style::FlatLabel &st) {
auto helper = Ui::Text::CustomEmojiHelper();
auto marked = helper.paletteDependent(Ui::AttentionMarkEmoji());
marked.append(QChar(' ')).append(std::move(text));
auto result = object_ptr<Ui::FlatLabel>(parent, st);
const auto raw = result.data();
raw->setMarkedText(marked, helper.context([=] { raw->update(); }));
return result;
}
[[nodiscard]] TextWithEntities BinaryWarningText() {
auto result = tr::lng_wallet_connect_sign_binary(tr::now, tr::bold);
result.append(QChar('\n'));
result.append(tr::lng_wallet_connect_sign_blind(tr::now));
return result;
}
[[nodiscard]] object_ptr<Ui::FlatLabel> MakeSignDataLabel(
not_null<QWidget*> parent,
const QString &text) {
return object_ptr<Ui::FlatLabel>(
parent,
rpl::single(Ui::Text::Wrapped(tr::marked(text), EntityType::Code)),
st::walletConnectSignDataLabel);
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakeSignDataBubble(
not_null<QWidget*> parent,
object_ptr<Ui::RpWidget> content,
Fn<void()> copy) {
auto result = MakeCommentBubble(
parent,
std::move(content),
st::windowBgOver);
const auto raw = result.data();
const auto button = Ui::CreateChild<Ui::AbstractButton>(raw);
raw->sizeValue() | rpl::on_next([=](QSize size) {
button->resize(size);
}, button->lifetime());
button->setPointerCursor(true);
button->setClickedCallback(std::move(copy));
button->raise();
return result;
}
[[nodiscard]] Fn<void()> CopySignData(
std::shared_ptr<Ui::Show> show,
const TonConnectSignData &data) {
return CopyTextCallback(
std::move(show),
data.data,
tr::lng_text_copied(tr::now));
}
void AddSignDataBubble(
not_null<Ui::VerticalLayout*> body,
object_ptr<Ui::RpWidget> content,
Fn<void()> copy) {
body->add(
MakeSignDataBubble(body, std::move(content), std::move(copy)),
st::giveawayGiftCodeTableMargin,
style::al_justify);
}
[[nodiscard]] TextWithEntities SignDataFieldText(
const TonConnectSignDataField &field) {
auto result = Ui::Text::Colorized(field.name + QChar(':'));
result.append(QChar(' ')).append(field.value);
return Ui::Text::Wrapped(std::move(result), EntityType::Code);
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakeSignDataFields(
not_null<QWidget*> parent,
const std::vector<TonConnectSignDataField> &fields) {
auto result = object_ptr<Ui::VerticalLayout>(parent);
const auto raw = result.data();
for (const auto &field : fields) {
const auto depth = std::min(field.depth, kFieldDepthShown);
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
rpl::single(SignDataFieldText(field)),
st::walletConnectSignFieldLabel),
style::margins(depth * st::walletConnectSignFieldIndent, 0, 0, 0));
}
return result;
}
void AddSignDataCell(
not_null<Ui::VerticalLayout*> body,
std::shared_ptr<Ui::Show> show,
const TonConnectSignData &data) {
auto copy = CopySignData(std::move(show), data);
if (data.fields.empty()) {
AddSignDataBubble(
body,
MakeSignDataLabel(body, data.schema),
std::move(copy));
body->add(
MakeWarningLabel(
body,
tr::marked(tr::lng_wallet_connect_sign_blind(tr::now)),
st::walletCommentCaptionLabel),
st::walletConnectSignCaptionMargin);
return;
}
AddSignDataBubble(
body,
MakeSignDataFields(body, data.fields),
std::move(copy));
body->add(
object_ptr<Ui::FlatLabel>(
body,
tr::lng_wallet_connect_sign_text_about(),
st::walletCommentCaptionLabel),
st::walletConnectSignCaptionMargin);
}
void AddSignDataContent(
not_null<Ui::VerticalLayout*> body,
std::shared_ptr<Ui::Show> show,
const TonConnectSignData &data) {
switch (data.type) {
case SignDataType::Text:
AddSignDataBubble(
body,
MakeSignDataLabel(body, data.data),
CopySignData(std::move(show), data));
body->add(
object_ptr<Ui::FlatLabel>(
body,
tr::lng_wallet_connect_sign_text_about(),
st::walletCommentCaptionLabel),
st::walletConnectSignCaptionMargin);
return;
case SignDataType::Binary:
AddSignDataBubble(
body,
MakeWarningLabel(body, BinaryWarningText(), st::walletCommentLabel),
CopySignData(std::move(show), data));
AddDetailsSkip(body);
return;
case SignDataType::Cell:
AddSignDataCell(body, std::move(show), data);
return;
}
Unexpected("Type in TonConnectRequestBox AddSignDataContent.");
}
void FillSignData(
not_null<Ui::GenericBox*> box,
not_null<State*> state,
const Context &context,
const TonConnectRequestBoxState &now) {
const auto body = state->body.data();
Ui::AddSubsectionTitle(
body,
tr::lng_wallet_connect_sign_data(),
DetailsTitlePadding());
AddSignDataContent(body, box->uiShow(), *now.signData);
AddDecisionButtons(
box,
state,
context,
tr::lng_cancel(),
tr::lng_wallet_connect_sign_button());
// WHY: a justified row gets its width only when the layout resizes, and
// the page is refilled after the box was laid out, so the data bubble
// would keep its label's natural width without this.
if (const auto width = body->widthNoMargins(); width > 0) {
body->resizeToWidth(width);
}
}
void FillDetails(
not_null<Ui::GenericBox*> box,
not_null<State*> state,
const Context &context,
const TonConnectRequestBoxState &now) {
const auto body = state->body.data();
if (const auto transfer = now.transfer.get()) {
const auto show = box->uiShow();
if (const auto shown = ShownEmulation(now)) {
FillEmulatedDetails(body, show, *transfer, *shown);
} else {
FillRequestDetails(body, show, context, *transfer);
}
AddDetailsSkip(body);
}
AddConfirmTail(box, state, context, now);
}
void ToggleBack(
not_null<Ui::GenericBox*> box,
not_null<State*> state,
const Context &context) {
if (state->details && !state->back) {
const auto back = Ui::CreateChild<Ui::IconButton>(
box.get(),
st::walletConnectBack);
back->setClickedCallback([=] {
Ui::PostponeCall(box, [=] {
SetDetails(box, state, context, false);
});
});
back->moveToLeft(0, 0);
back->raise();
state->back = back;
}
if (const auto back = state->back.data()) {
back->setVisible(state->details);
}
}
void Rebuild(
not_null<Ui::GenericBox*> box,
not_null<State*> state,
const Context &context,
const TonConnectRequestBoxState &now) {
state->confirm = nullptr;
state->decline = nullptr;
state->cancel = nullptr;
state->unlock = nullptr;
state->fee = nullptr;
state->error = nullptr;
state->body->clear();
if (now.phase != Phase::Confirm) {
state->details = false;
}
switch (now.phase) {
case Phase::Loading: FillLoading(box); break;
case Phase::Locked:
FillKeyNeeded(
box,
state,
tr::lng_wallet_connect_request_locked(),
tr::lng_wallet_connect_request_unlock(),
context.unlock);
break;
case Phase::Restore:
FillKeyNeeded(
box,
state,
tr::lng_wallet_connect_restore(),
tr::lng_wallet_restore_title(),
context.restore);
break;
case Phase::Notice: FillNotice(box, state, now); break;
case Phase::Unhandled: FillUnhandled(box, state, context, now); break;
case Phase::Confirm:
if (now.signData) {
FillSignData(box, state, context, now);
} else if (state->details) {
FillDetails(box, state, context, now);
} else {
FillSheet(box, state, context, now);
}
break;
}
ToggleBack(box, state, context);
Update(state, now, anim::type::instant);
}
// WHY: the details view is a page of the same box, not a box over it, so
// the flow's close, expiry and dismissal reach it with no wiring of their
// own, and both pages build Decline / Confirm in AddConfirmTail.
void SetDetails(
not_null<Ui::GenericBox*> box,
not_null<State*> state,
const Context &context,
bool details) {
if (!state->built
|| state->built->phase != Phase::Confirm
|| state->details == details) {
return;
}
state->details = details;
Rebuild(box, state, context, *state->built);
box->scrollToY(0);
}
} // namespace
void TonConnectRequestBox(
not_null<Ui::GenericBox*> box,
TonConnectRequestBoxArgs args) {
box->setWidth(st::boxWideWidth);
box->setStyle(st::walletConnectBox);
box->setNoContentMargin(true);
const auto state = box->lifetime().make_state<State>();
const auto reported = std::make_shared<bool>(false);
const auto dismiss = [=, dismissed = args.dismissed] {
if (!std::exchange(*reported, true) && dismissed) {
dismissed();
}
};
box->boxClosing() | rpl::on_next(dismiss, box->lifetime());
box->lifetime().add(dismiss);
state->header = AddTonConnectHeader(box->verticalLayout(), args.session);
state->body = box->verticalLayout()->add(
object_ptr<Ui::VerticalLayout>(box),
style::margins(),
style::al_justify);
const auto context = Context{
.session = args.session,
.unlock = args.unlock,
.restore = args.restore,
.confirm = args.confirm,
.decline = args.decline,
};
std::move(
args.state
) | rpl::on_next([=](const TonConnectRequestBoxState &now) {
const auto first = !state->built;
state->header(
HeaderStateFor(now),
first ? anim::type::instant : anim::type::normal);
if (!first && SameStructure(*state->built, now)) {
Update(state, now, anim::type::normal);
} else {
Rebuild(box, state, context, now);
}
state->built = now;
}, box->lifetime());
}
} // namespace Wallet