Allow exporting unique gifts to blockchain.

This commit is contained in:
John Preston 2025-01-21 13:38:04 +04:00
parent 0d07d238bc
commit 37c7b0c6d1
2 changed files with 124 additions and 25 deletions

View file

@ -3365,6 +3365,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_transferred_about" = "{name} was successfully transferred to {recipient}."; "lng_gift_transferred_about" = "{name} was successfully transferred to {recipient}.";
"lng_gift_transfer_title" = "Transfer {name}"; "lng_gift_transfer_title" = "Transfer {name}";
"lng_gift_transfer_via_blockchain" = "Send via Blockchain"; "lng_gift_transfer_via_blockchain" = "Send via Blockchain";
"lng_gift_transfer_password_title" = "Two-step verification";
"lng_gift_transfer_password_description" = "Please enter your password to transfer.";
"lng_gift_transfer_password_about" = "You can withdraw only if you have:";
"lng_gift_transfer_unlocks_days#one" = "unlocks in {count} day"; "lng_gift_transfer_unlocks_days#one" = "unlocks in {count} day";
"lng_gift_transfer_unlocks_days#other" = "unlocks in {count} days"; "lng_gift_transfer_unlocks_days#other" = "unlocks in {count} days";
"lng_gift_transfer_unlocks_hours#one" = "unlocks in {count} hour"; "lng_gift_transfer_unlocks_hours#one" = "unlocks in {count} hour";

View file

@ -9,7 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "api/api_credits.h" #include "api/api_credits.h"
#include "api/api_cloud_password.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "boxes/passcode_box.h"
#include "data/data_star_gift.h" #include "data/data_star_gift.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "boxes/filters/edit_filter_chats_list.h" // CreatePe...tionSubtitle. #include "boxes/filters/edit_filter_chats_list.h" // CreatePe...tionSubtitle.
@ -22,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/basic_click_handlers.h"
#include "ui/empty_userpic.h" #include "ui/empty_userpic.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/vertical_list.h" #include "ui/vertical_list.h"
@ -42,13 +45,15 @@ public:
Controller( Controller(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
std::shared_ptr<Data::UniqueGift> gift, std::shared_ptr<Data::UniqueGift> gift,
Data::SavedStarGiftId savedId,
Fn<void(not_null<PeerData*>)> choose); Fn<void(not_null<PeerData*>)> choose);
void initExport(not_null<PeerListBox*> box);
void noSearchSubmit(); void noSearchSubmit();
private: private:
void prepareViewHook() override; void prepareViewHook() override;
void setupExportOption();
bool overrideKeyboardNavigation( bool overrideKeyboardNavigation(
int direction, int direction,
@ -59,34 +64,115 @@ private:
not_null<UserData*> user) override; not_null<UserData*> user) override;
void rowClicked(not_null<PeerListRow*> row) override; void rowClicked(not_null<PeerListRow*> row) override;
const not_null<Window::SessionController*> _window;
const std::shared_ptr<Data::UniqueGift> _gift; const std::shared_ptr<Data::UniqueGift> _gift;
const Data::SavedStarGiftId _savedId;
const Fn<void(not_null<PeerData*>)> _choose; const Fn<void(not_null<PeerData*>)> _choose;
ExportOption _exportOption; ExportOption _exportOption;
}; };
void ExportOnBlockchain(
not_null<Window::SessionController*> window,
not_null<Ui::RpWidget*> parent,
Data::SavedStarGiftId giftId,
Fn<void()> waitFinished) {
struct State {
bool loading = false;
rpl::lifetime lifetime;
};
const auto state = std::make_shared<State>();
const auto session = &window->session();
const auto show = window->uiShow();
session->api().cloudPassword().reload();
session->api().request(
MTPpayments_GetStarGiftWithdrawalUrl(
Api::InputSavedStarGiftId(giftId),
MTP_inputCheckPasswordEmpty())
).fail([=](const MTP::Error &error) {
auto box = PrePasswordErrorBox(
error.type(),
session,
TextWithEntities{
tr::lng_gift_transfer_password_about(tr::now),
});
if (box) {
waitFinished();
show->show(std::move(box));
return;
}
state->lifetime = session->api().cloudPassword().state(
) | rpl::take(
1
) | rpl::start_with_next([=](const Core::CloudPasswordState &pass) {
waitFinished();
auto fields = PasscodeBox::CloudFields::From(pass);
fields.customTitle = tr::lng_gift_transfer_password_title();
fields.customDescription
= tr::lng_gift_transfer_password_description(tr::now);
fields.customSubmitButton = tr::lng_passcode_submit();
fields.customCheckCallback = crl::guard(parent, [=](
const Core::CloudPasswordResult &result,
QPointer<PasscodeBox> box) {
const auto done = [=](const QString &result) {
};
using ExportUrl = MTPpayments_StarGiftWithdrawalUrl;
session->api().request(
MTPpayments_GetStarGiftWithdrawalUrl(
Api::InputSavedStarGiftId(giftId),
result.result)
).done([=](const ExportUrl &result) {
UrlClickHandler::Open(qs(result.data().vurl()));
if (box) {
box->closeBox();
}
}).fail([=](const MTP::Error &error) {
const auto message = error.type();
if (box && !box->handleCustomCheckError(message)) {
show->showToast(message);
}
}).send();
});
show->show(Box<PasscodeBox>(session, fields));
});
}).send();
}
[[nodiscard]] ExportOption MakeExportOption( [[nodiscard]] ExportOption MakeExportOption(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
not_null<Ui::RpWidget*> parent,
Data::SavedStarGiftId giftId,
TimeId when) { TimeId when) {
struct State {
bool exporting = false;
};
const auto state = std::make_shared<State>();
const auto activate = [=] { const auto activate = [=] {
const auto now = base::unixtime::now(); const auto now = base::unixtime::now();
const auto left = (when > now) ? (when - now) : 0; const auto left = (when > now) ? (when - now) : 0;
const auto hours = left ? std::max((left + 1800) / 3600, 1) : 0; const auto hours = left ? std::max((left + 1800) / 3600, 1) : 0;
if (!hours) {
if (state->exporting) {
return;
}
state->exporting = true;
ExportOnBlockchain(window, parent, giftId, [=] {
state->exporting = false;
});
return;
}
window->show(Ui::MakeInformBox({ window->show(Ui::MakeInformBox({
.text = (!hours .text = tr::lng_gift_transfer_unlocks_about(
? tr::lng_gift_transfer_unlocks_update_about() lt_when,
: tr::lng_gift_transfer_unlocks_about( ((hours >= 24)
lt_when, ? tr::lng_gift_transfer_unlocks_when_days(
((hours >= 24) lt_count,
? tr::lng_gift_transfer_unlocks_when_days( rpl::single((hours / 24) * 1.))
lt_count, : tr::lng_gift_transfer_unlocks_when_hours(
rpl::single((hours / 24) * 1.)) lt_count,
: tr::lng_gift_transfer_unlocks_when_hours( rpl::single(hours * 1.)))),
lt_count, .title = tr::lng_gift_transfer_unlocks_title(),
rpl::single(hours * 1.))))),
.title = (!hours
? tr::lng_gift_transfer_unlocks_update_title()
: tr::lng_gift_transfer_unlocks_title()),
})); }));
}; };
@ -131,10 +217,14 @@ private:
const style::PeerListItem &computeSt( const style::PeerListItem &computeSt(
const style::PeerListItem &st) const override { const style::PeerListItem &st) const override {
return _available ? st::recentPeersSpecialName : st; _st = st;
_st.namePosition.setY(
st::recentPeersSpecialName.namePosition.y());
return _available ? _st : st;
} }
private: private:
mutable style::PeerListItem _st;
bool _available = false; bool _available = false;
}; };
@ -229,18 +319,26 @@ private:
Controller::Controller( Controller::Controller(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
std::shared_ptr<Data::UniqueGift> gift, std::shared_ptr<Data::UniqueGift> gift,
Data::SavedStarGiftId savedId,
Fn<void(not_null<PeerData*>)> choose) Fn<void(not_null<PeerData*>)> choose)
: ContactsBoxController(&window->session()) : ContactsBoxController(&window->session())
, _window(window)
, _gift(std::move(gift)) , _gift(std::move(gift))
, _savedId(savedId)
, _choose(std::move(choose)) { , _choose(std::move(choose)) {
if (const auto when = _gift->exportAt) { if (_gift->exportAt) {
_exportOption = MakeExportOption(window, when);
}
if (_exportOption.content) {
setStyleOverrides(&st::peerListSmallSkips); setStyleOverrides(&st::peerListSmallSkips);
} }
} }
void Controller::initExport(not_null<PeerListBox*> box) {
if (const auto when = _gift->exportAt) {
_exportOption = MakeExportOption(_window, box, _savedId, when);
delegate()->peerListSetAboveWidget(std::move(_exportOption.content));
delegate()->peerListRefreshRows();
}
}
void Controller::noSearchSubmit() { void Controller::noSearchSubmit() {
if (const auto onstack = _exportOption.activate) { if (const auto onstack = _exportOption.activate) {
onstack(); onstack();
@ -259,11 +357,6 @@ void Controller::prepareViewHook() {
delegate()->peerListSetTitle(tr::lng_gift_transfer_title( delegate()->peerListSetTitle(tr::lng_gift_transfer_title(
lt_name, lt_name,
rpl::single(UniqueGiftName(*_gift)))); rpl::single(UniqueGiftName(*_gift))));
setupExportOption();
}
void Controller::setupExportOption() {
delegate()->peerListSetAboveWidget(std::move(_exportOption.content));
} }
std::unique_ptr<PeerListRow> Controller::createRow( std::unique_ptr<PeerListRow> Controller::createRow(
@ -400,11 +493,14 @@ void ShowTransferGiftBox(
auto controller = std::make_unique<Controller>( auto controller = std::make_unique<Controller>(
window, window,
gift, gift,
savedId,
[=](not_null<PeerData*> peer) { [=](not_null<PeerData*> peer) {
ShowTransferToBox(window, peer, gift, savedId); ShowTransferToBox(window, peer, gift, savedId);
}); });
const auto controllerRaw = controller.get(); const auto controllerRaw = controller.get();
auto initBox = [=](not_null<PeerListBox*> box) { auto initBox = [=](not_null<PeerListBox*> box) {
controllerRaw->initExport(box);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
box->noSearchSubmits() | rpl::start_with_next([=] { box->noSearchSubmits() | rpl::start_with_next([=] {