/* 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 "base/unixtime.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/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. namespace { struct ExportOption { object_ptr content = { nullptr }; Fn overrideKey; Fn activate; }; class Controller final : public ContactsBoxController { public: Controller( not_null window, std::shared_ptr gift, Fn)> choose); void noSearchSubmit(); private: void prepareViewHook() override; void setupExportOption(); bool overrideKeyboardNavigation( int direction, int fromIndex, int toIndex) override; std::unique_ptr createRow( not_null user) override; void rowClicked(not_null row) override; const std::shared_ptr _gift; const Fn)> _choose; ExportOption _exportOption; }; [[nodiscard]] ExportOption MakeExportOption( not_null window, TimeId when) { const auto activate = [=] { const auto now = base::unixtime::now(); const auto left = (when > now) ? (when - now) : 0; const auto hours = left ? std::max((left + 1800) / 3600, 1) : 0; window->show(Ui::MakeInformBox({ .text = (!hours ? tr::lng_gift_transfer_unlocks_update_about() : 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 = (!hours ? tr::lng_gift_transfer_unlocks_update_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 { return _available ? st::recentPeersSpecialName : st; } private: bool _available = false; }; class ExportController final : public PeerListController { public: ExportController( not_null session, TimeId when, Fn activate) : _session(session) , _when(when) , _activate(std::move(activate)) { } void prepare() override { delegate()->peerListAppendRow( std::make_unique(_when)); delegate()->peerListRefreshRows(); } void loadMoreRows() override { } void rowClicked(not_null row) override { _activate(); } Main::Session &session() const override { return *_session; } private: const not_null _session; TimeId _when = 0; Fn _activate; }; auto result = object_ptr((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( 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, std::shared_ptr gift, Fn)> choose) : ContactsBoxController(&window->session()) , _gift(std::move(gift)) , _choose(std::move(choose)) { if (const auto when = _gift->exportAt) { _exportOption = MakeExportOption(window, when); } if (_exportOption.content) { setStyleOverrides(&st::peerListSmallSkips); } } 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)))); setupExportOption(); } void Controller::setupExportOption() { delegate()->peerListSetAboveWidget(std::move(_exportOption.content)); } std::unique_ptr Controller::createRow( not_null user) { if (user->isSelf() || user->isBot() || user->isServiceUser() || user->isInaccessible()) { return nullptr; } return ContactsBoxController::createRow(user); } void Controller::rowClicked(not_null row) { _choose(row->peer()); } void TransferGift( not_null window, not_null to, std::shared_ptr gift, MsgId messageId, Fn done) { Expects(to->isUser()); const auto session = &window->session(); const auto weak = base::make_weak(window); auto formDone = [=]( Payments::CheckoutResult result, const MTPUpdates *updates) { if (result == Payments::CheckoutResult::Paid && updates) { if (const auto strong = weak.get()) { Ui::ShowGiftTransferredToast(strong, to, *updates); } } done(result); }; if (gift->starsForTransfer <= 0) { session->api().request(MTPpayments_TransferStarGift( MTP_int(messageId.bare), to->asUser()->inputUser )).done([=](const MTPUpdates &result) { session->api().applyUpdates(result); formDone(Payments::CheckoutResult::Paid, &result); }).fail([=](const MTP::Error &error) { if (const auto strong = weak.get()) { strong->showToast(error.type()); } formDone(Payments::CheckoutResult::Failed, nullptr); }).send(); return; } Ui::RequestStarsFormAndSubmit( window, MTP_inputInvoiceStarGiftTransfer( MTP_int(messageId.bare), to->asUser()->inputUser), std::move(formDone)); } void ShowTransferToBox( not_null controller, not_null peer, std::shared_ptr gift, MsgId msgId) { const auto stars = gift->starsForTransfer; controller->show(Box([=](not_null 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(); 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::Paid) { state->sent = false; } else { controller->showPeerHistory(peer); if (const auto strong = weak.data()) { strong->closeBox(); } } }; TransferGift(controller, peer, gift, msgId, 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, std::shared_ptr gift, MsgId msgId) { auto controller = std::make_unique( window, gift, [=](not_null peer) { ShowTransferToBox(window, peer, gift, msgId); }); const auto controllerRaw = controller.get(); auto initBox = [=](not_null box) { box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); box->noSearchSubmits() | rpl::start_with_next([=] { controllerRaw->noSearchSubmit(); }, box->lifetime()); }; window->show( Box(std::move(controller), std::move(initBox)), Ui::LayerOption::KeepOther); }