| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568 |
- /*
- 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 "boxes/transfer_gift_box.h"
- #include "apiwrap.h"
- #include "api/api_credits.h"
- #include "api/api_cloud_password.h"
- #include "base/unixtime.h"
- #include "boxes/passcode_box.h"
- #include "data/data_session.h"
- #include "data/data_star_gift.h"
- #include "data/data_user.h"
- #include "boxes/filters/edit_filter_chats_list.h" // CreatePe...tionSubtitle.
- #include "boxes/peer_list_box.h"
- #include "boxes/peer_list_controllers.h"
- #include "boxes/star_gift_box.h"
- #include "lang/lang_keys.h"
- #include "main/main_session.h"
- #include "payments/payments_checkout_process.h"
- #include "ui/boxes/confirm_box.h"
- #include "ui/layers/generic_box.h"
- #include "ui/text/text_utilities.h"
- #include "ui/basic_click_handlers.h"
- #include "ui/empty_userpic.h"
- #include "ui/painter.h"
- #include "ui/vertical_list.h"
- #include "window/window_session_controller.h"
- #include "styles/style_boxes.h" // peerListSingleRow.
- #include "styles/style_dialogs.h" // recentPeersSpecialName.
- #include "styles/style_layers.h" // boxLabel.
- namespace {
- struct ExportOption {
- object_ptr<Ui::RpWidget> content = { nullptr };
- Fn<bool(int, int, int)> overrideKey;
- Fn<void()> activate;
- };
- class Controller final : public ContactsBoxController {
- public:
- Controller(
- not_null<Window::SessionController*> window,
- std::shared_ptr<Data::UniqueGift> gift,
- Data::SavedStarGiftId savedId,
- Fn<void(not_null<PeerData*>, Fn<void()>)> choose);
- void init(not_null<PeerListBox*> box);
- void noSearchSubmit();
- private:
- void prepareViewHook() override;
- bool overrideKeyboardNavigation(
- int direction,
- int fromIndex,
- int toIndex) override;
- std::unique_ptr<PeerListRow> createRow(
- not_null<UserData*> user) override;
- void rowClicked(not_null<PeerListRow*> row) override;
- const not_null<Window::SessionController*> _window;
- const std::shared_ptr<Data::UniqueGift> _gift;
- const Data::SavedStarGiftId _giftId;
- const Fn<void(not_null<PeerData*>, Fn<void()>)> _choose;
- ExportOption _exportOption;
- QPointer<PeerListBox> _box;
- };
- void ConfirmExportBox(
- not_null<Ui::GenericBox*> box,
- std::shared_ptr<Data::UniqueGift> gift,
- Fn<void(Fn<void()> close)> confirmed) {
- box->setTitle(tr::lng_gift_transfer_confirm_title());
- box->addRow(object_ptr<Ui::FlatLabel>(
- box,
- tr::lng_gift_transfer_confirm_text(
- lt_name,
- rpl::single(Ui::Text::Bold(UniqueGiftName(*gift))),
- Ui::Text::WithEntities),
- st::boxLabel));
- box->addButton(tr::lng_gift_transfer_confirm_button(), [=] {
- confirmed([weak = Ui::MakeWeak(box)] {
- if (const auto strong = weak.data()) {
- strong->closeBox();
- }
- });
- });
- box->addButton(tr::lng_cancel(), [=] {
- box->closeBox();
- });
- }
- void ExportOnBlockchain(
- not_null<Window::SessionController*> window,
- not_null<Ui::RpWidget*> parent,
- std::shared_ptr<Data::UniqueGift> gift,
- Data::SavedStarGiftId giftId,
- Fn<void()> boxShown,
- Fn<void()> wentToUrl) {
- 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) {
- show->show(std::move(box));
- boxShown();
- return;
- }
- state->lifetime = session->api().cloudPassword().state(
- ) | rpl::take(
- 1
- ) | rpl::start_with_next([=](const Core::CloudPasswordState &pass) {
- 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) {
- using ExportUrl = MTPpayments_StarGiftWithdrawalUrl;
- session->api().request(
- MTPpayments_GetStarGiftWithdrawalUrl(
- Api::InputSavedStarGiftId(giftId),
- result.result)
- ).done([=](const ExportUrl &result) {
- UrlClickHandler::Open(qs(result.data().vurl()));
- wentToUrl();
- 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));
- boxShown();
- });
- }).send();
- }
- [[nodiscard]] ExportOption MakeExportOption(
- not_null<Window::SessionController*> window,
- not_null<PeerListBox*> box,
- std::shared_ptr<Data::UniqueGift> gift,
- Data::SavedStarGiftId giftId,
- TimeId when) {
- struct State {
- bool exporting = false;
- };
- const auto state = std::make_shared<State>();
- const auto activate = [=] {
- const auto now = base::unixtime::now();
- const auto weak = Ui::MakeWeak(box);
- const auto left = (when > now) ? (when - now) : 0;
- const auto hours = left ? std::max((left + 1800) / 3600, 1) : 0;
- if (!hours) {
- window->show(Box(ConfirmExportBox, gift, [=](Fn<void()> close) {
- if (state->exporting) {
- return;
- }
- state->exporting = true;
- ExportOnBlockchain(window, box, gift, giftId, [=] {
- state->exporting = false;
- close();
- }, [=] {
- if (const auto strong = weak.data()) {
- strong->closeBox();
- }
- close();
- });
- }));
- return;
- }
- window->show(Ui::MakeInformBox({
- .text = tr::lng_gift_transfer_unlocks_about(
- lt_when,
- ((hours >= 24)
- ? tr::lng_gift_transfer_unlocks_when_days(
- lt_count,
- rpl::single((hours / 24) * 1.))
- : tr::lng_gift_transfer_unlocks_when_hours(
- lt_count,
- rpl::single(hours * 1.)))),
- .title = tr::lng_gift_transfer_unlocks_title(),
- }));
- };
- class ExportRow final : public PeerListRow {
- public:
- explicit ExportRow(TimeId when)
- : PeerListRow(Data::FakePeerIdForJustName("ton-export").value) {
- const auto now = base::unixtime::now();
- _available = (when <= now);
- if (const auto left = when - now; left > 0) {
- const auto hours = std::max((left + 1800) / 3600, 1);
- const auto days = hours / 24;
- setCustomStatus(days
- ? tr::lng_gift_transfer_unlocks_days(
- tr::now,
- lt_count,
- days)
- : tr::lng_gift_transfer_unlocks_hours(
- tr::now,
- lt_count,
- hours));
- }
- }
- QString generateName() override {
- return tr::lng_gift_transfer_via_blockchain(tr::now);
- }
- QString generateShortName() override {
- return generateName();
- }
- auto generatePaintUserpicCallback(bool forceRound)
- -> PaintRoundImageCallback override {
- return [=](
- Painter &p,
- int x,
- int y,
- int outerWidth,
- int size) mutable {
- Ui::EmptyUserpic::PaintCurrency(p, x, y, outerWidth, size);
- };
- }
- const style::PeerListItem &computeSt(
- const style::PeerListItem &st) const override {
- _st = st;
- _st.namePosition.setY(
- st::recentPeersSpecialName.namePosition.y());
- return _available ? _st : st;
- }
- private:
- mutable style::PeerListItem _st;
- bool _available = false;
- };
- class ExportController final : public PeerListController {
- public:
- ExportController(
- not_null<Main::Session*> session,
- TimeId when,
- Fn<void()> activate)
- : _session(session)
- , _when(when)
- , _activate(std::move(activate)) {
- }
- void prepare() override {
- delegate()->peerListAppendRow(
- std::make_unique<ExportRow>(_when));
- delegate()->peerListRefreshRows();
- }
- void loadMoreRows() override {
- }
- void rowClicked(not_null<PeerListRow*> row) override {
- _activate();
- }
- Main::Session &session() const override {
- return *_session;
- }
- private:
- const not_null<Main::Session*> _session;
- TimeId _when = 0;
- Fn<void()> _activate;
- };
- auto result = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
- const auto container = result.data();
- Ui::AddSkip(container);
- const auto delegate = container->lifetime().make_state<
- PeerListContentDelegateSimple
- >();
- const auto controller = container->lifetime().make_state<
- ExportController
- >(&window->session(), when, activate);
- controller->setStyleOverrides(&st::peerListSingleRow);
- const auto content = container->add(object_ptr<PeerListContent>(
- container,
- controller));
- delegate->setContent(content);
- controller->setDelegate(delegate);
- Ui::AddSkip(container);
- container->add(CreatePeerListSectionSubtitle(
- container,
- tr::lng_contacts_header()));
- const auto overrideKey = [=](int direction, int from, int to) {
- if (!content->isVisible()) {
- return false;
- } else if (direction > 0 && from < 0 && to >= 0) {
- if (content->hasSelection()) {
- const auto was = content->selectedIndex();
- const auto now = content->selectSkip(1).reallyMovedTo;
- if (was != now) {
- return true;
- }
- content->clearSelection();
- } else {
- content->selectSkip(1);
- return true;
- }
- } else if (direction < 0 && to < 0) {
- if (!content->hasSelection()) {
- content->selectLast();
- } else if (from >= 0 || content->hasSelection()) {
- content->selectSkip(-1);
- }
- }
- return false;
- };
- return {
- .content = std::move(result),
- .overrideKey = overrideKey,
- .activate = activate,
- };
- }
- Controller::Controller(
- not_null<Window::SessionController*> window,
- std::shared_ptr<Data::UniqueGift> gift,
- Data::SavedStarGiftId giftId,
- Fn<void(not_null<PeerData*>, Fn<void()>)> choose)
- : ContactsBoxController(&window->session())
- , _window(window)
- , _gift(std::move(gift))
- , _giftId(giftId)
- , _choose(std::move(choose)) {
- if (_gift->exportAt) {
- setStyleOverrides(&st::peerListSmallSkips);
- }
- }
- void Controller::init(not_null<PeerListBox*> box) {
- _box = box;
- if (const auto when = _gift->exportAt) {
- _exportOption = MakeExportOption(_window, box, _gift, _giftId, when);
- delegate()->peerListSetAboveWidget(std::move(_exportOption.content));
- delegate()->peerListRefreshRows();
- }
- }
- void Controller::noSearchSubmit() {
- if (const auto onstack = _exportOption.activate) {
- onstack();
- }
- }
- bool Controller::overrideKeyboardNavigation(
- int direction,
- int fromIndex,
- int toIndex) {
- return _exportOption.overrideKey
- && _exportOption.overrideKey(direction, fromIndex, toIndex);
- }
- void Controller::prepareViewHook() {
- delegate()->peerListSetTitle(tr::lng_gift_transfer_title(
- lt_name,
- rpl::single(UniqueGiftName(*_gift))));
- }
- std::unique_ptr<PeerListRow> Controller::createRow(
- not_null<UserData*> user) {
- if (user->isSelf()
- || user->isBot()
- || user->isServiceUser()
- || user->isInaccessible()) {
- return nullptr;
- }
- return ContactsBoxController::createRow(user);
- }
- void Controller::rowClicked(not_null<PeerListRow*> row) {
- _choose(row->peer(), [parentBox = _box] {
- if (const auto strong = parentBox.data()) {
- strong->closeBox();
- }
- });
- }
- void TransferGift(
- not_null<Window::SessionController*> window,
- not_null<PeerData*> to,
- std::shared_ptr<Data::UniqueGift> gift,
- Data::SavedStarGiftId savedId,
- Fn<void(Payments::CheckoutResult)> done) {
- Expects(to->isUser());
- const auto session = &window->session();
- const auto weak = base::make_weak(window);
- auto formDone = [=](
- Payments::CheckoutResult result,
- const MTPUpdates *updates) {
- done(result);
- if (result == Payments::CheckoutResult::Paid) {
- if (const auto strong = weak.get()) {
- strong->session().data().notifyGiftUpdate({
- .id = savedId,
- .action = Data::GiftUpdate::Action::Transfer,
- });
- Ui::ShowGiftTransferredToast(strong, to, *gift);
- }
- }
- };
- if (gift->starsForTransfer <= 0) {
- session->api().request(MTPpayments_TransferStarGift(
- Api::InputSavedStarGiftId(savedId),
- to->input
- )).done([=](const MTPUpdates &result) {
- session->api().applyUpdates(result);
- formDone(Payments::CheckoutResult::Paid, &result);
- }).fail([=](const MTP::Error &error) {
- formDone(Payments::CheckoutResult::Failed, nullptr);
- if (const auto strong = weak.get()) {
- strong->showToast(error.type());
- }
- }).send();
- return;
- }
- Ui::RequestStarsFormAndSubmit(
- window,
- MTP_inputInvoiceStarGiftTransfer(
- Api::InputSavedStarGiftId(savedId),
- to->input),
- std::move(formDone));
- }
- void ShowTransferToBox(
- not_null<Window::SessionController*> controller,
- not_null<PeerData*> peer,
- std::shared_ptr<Data::UniqueGift> gift,
- Data::SavedStarGiftId savedId,
- Fn<void()> closeParentBox) {
- const auto stars = gift->starsForTransfer;
- controller->show(Box([=](not_null<Ui::GenericBox*> box) {
- box->setTitle(tr::lng_gift_transfer_title(
- lt_name,
- rpl::single(UniqueGiftName(*gift))));
- auto transfer = (stars > 0)
- ? tr::lng_gift_transfer_button_for(
- lt_price,
- tr::lng_action_gift_for_stars(
- lt_count,
- rpl::single(stars * 1.)))
- : tr::lng_gift_transfer_button();
- struct State {
- bool sent = false;
- };
- const auto state = std::make_shared<State>();
- auto callback = [=] {
- if (state->sent) {
- return;
- }
- state->sent = true;
- const auto weak = Ui::MakeWeak(box);
- const auto done = [=](Payments::CheckoutResult result) {
- if (result == Payments::CheckoutResult::Cancelled) {
- closeParentBox();
- if (const auto strong = weak.data()) {
- strong->closeBox();
- }
- } else if (result != Payments::CheckoutResult::Paid) {
- state->sent = false;
- } else {
- if (savedId.isUser()) {
- controller->showPeerHistory(peer);
- }
- closeParentBox();
- if (const auto strong = weak.data()) {
- strong->closeBox();
- }
- }
- };
- TransferGift(controller, peer, gift, savedId, done);
- };
- Ui::ConfirmBox(box, {
- .text = (stars > 0)
- ? tr::lng_gift_transfer_sure_for(
- lt_name,
- rpl::single(Ui::Text::Bold(UniqueGiftName(*gift))),
- lt_recipient,
- rpl::single(Ui::Text::Bold(peer->shortName())),
- lt_price,
- tr::lng_action_gift_for_stars(
- lt_count,
- rpl::single(stars * 1.),
- Ui::Text::Bold),
- Ui::Text::WithEntities)
- : tr::lng_gift_transfer_sure(
- lt_name,
- rpl::single(Ui::Text::Bold(UniqueGiftName(*gift))),
- lt_recipient,
- rpl::single(Ui::Text::Bold(peer->shortName())),
- Ui::Text::WithEntities),
- .confirmed = std::move(callback),
- .confirmText = std::move(transfer),
- });
- }));
- }
- } // namespace
- void ShowTransferGiftBox(
- not_null<Window::SessionController*> window,
- std::shared_ptr<Data::UniqueGift> gift,
- Data::SavedStarGiftId savedId) {
- auto controller = std::make_unique<Controller>(
- window,
- gift,
- savedId,
- [=](not_null<PeerData*> peer, Fn<void()> done) {
- ShowTransferToBox(window, peer, gift, savedId, done);
- });
- const auto controllerRaw = controller.get();
- auto initBox = [=](not_null<PeerListBox*> box) {
- controllerRaw->init(box);
- box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
- box->noSearchSubmits() | rpl::start_with_next([=] {
- controllerRaw->noSearchSubmit();
- }, box->lifetime());
- };
- window->show(
- Box<PeerListBox>(std::move(controller), std::move(initBox)),
- Ui::LayerOption::KeepOther);
- }
|