transfer_gift_box.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "boxes/transfer_gift_box.h"
  8. #include "apiwrap.h"
  9. #include "api/api_credits.h"
  10. #include "api/api_cloud_password.h"
  11. #include "base/unixtime.h"
  12. #include "boxes/passcode_box.h"
  13. #include "data/data_session.h"
  14. #include "data/data_star_gift.h"
  15. #include "data/data_user.h"
  16. #include "boxes/filters/edit_filter_chats_list.h" // CreatePe...tionSubtitle.
  17. #include "boxes/peer_list_box.h"
  18. #include "boxes/peer_list_controllers.h"
  19. #include "boxes/star_gift_box.h"
  20. #include "lang/lang_keys.h"
  21. #include "main/main_session.h"
  22. #include "payments/payments_checkout_process.h"
  23. #include "ui/boxes/confirm_box.h"
  24. #include "ui/layers/generic_box.h"
  25. #include "ui/text/text_utilities.h"
  26. #include "ui/basic_click_handlers.h"
  27. #include "ui/empty_userpic.h"
  28. #include "ui/painter.h"
  29. #include "ui/vertical_list.h"
  30. #include "window/window_session_controller.h"
  31. #include "styles/style_boxes.h" // peerListSingleRow.
  32. #include "styles/style_dialogs.h" // recentPeersSpecialName.
  33. #include "styles/style_layers.h" // boxLabel.
  34. namespace {
  35. struct ExportOption {
  36. object_ptr<Ui::RpWidget> content = { nullptr };
  37. Fn<bool(int, int, int)> overrideKey;
  38. Fn<void()> activate;
  39. };
  40. class Controller final : public ContactsBoxController {
  41. public:
  42. Controller(
  43. not_null<Window::SessionController*> window,
  44. std::shared_ptr<Data::UniqueGift> gift,
  45. Data::SavedStarGiftId savedId,
  46. Fn<void(not_null<PeerData*>, Fn<void()>)> choose);
  47. void init(not_null<PeerListBox*> box);
  48. void noSearchSubmit();
  49. private:
  50. void prepareViewHook() override;
  51. bool overrideKeyboardNavigation(
  52. int direction,
  53. int fromIndex,
  54. int toIndex) override;
  55. std::unique_ptr<PeerListRow> createRow(
  56. not_null<UserData*> user) override;
  57. void rowClicked(not_null<PeerListRow*> row) override;
  58. const not_null<Window::SessionController*> _window;
  59. const std::shared_ptr<Data::UniqueGift> _gift;
  60. const Data::SavedStarGiftId _giftId;
  61. const Fn<void(not_null<PeerData*>, Fn<void()>)> _choose;
  62. ExportOption _exportOption;
  63. QPointer<PeerListBox> _box;
  64. };
  65. void ConfirmExportBox(
  66. not_null<Ui::GenericBox*> box,
  67. std::shared_ptr<Data::UniqueGift> gift,
  68. Fn<void(Fn<void()> close)> confirmed) {
  69. box->setTitle(tr::lng_gift_transfer_confirm_title());
  70. box->addRow(object_ptr<Ui::FlatLabel>(
  71. box,
  72. tr::lng_gift_transfer_confirm_text(
  73. lt_name,
  74. rpl::single(Ui::Text::Bold(UniqueGiftName(*gift))),
  75. Ui::Text::WithEntities),
  76. st::boxLabel));
  77. box->addButton(tr::lng_gift_transfer_confirm_button(), [=] {
  78. confirmed([weak = Ui::MakeWeak(box)] {
  79. if (const auto strong = weak.data()) {
  80. strong->closeBox();
  81. }
  82. });
  83. });
  84. box->addButton(tr::lng_cancel(), [=] {
  85. box->closeBox();
  86. });
  87. }
  88. void ExportOnBlockchain(
  89. not_null<Window::SessionController*> window,
  90. not_null<Ui::RpWidget*> parent,
  91. std::shared_ptr<Data::UniqueGift> gift,
  92. Data::SavedStarGiftId giftId,
  93. Fn<void()> boxShown,
  94. Fn<void()> wentToUrl) {
  95. struct State {
  96. bool loading = false;
  97. rpl::lifetime lifetime;
  98. };
  99. const auto state = std::make_shared<State>();
  100. const auto session = &window->session();
  101. const auto show = window->uiShow();
  102. session->api().cloudPassword().reload();
  103. session->api().request(
  104. MTPpayments_GetStarGiftWithdrawalUrl(
  105. Api::InputSavedStarGiftId(giftId),
  106. MTP_inputCheckPasswordEmpty())
  107. ).fail([=](const MTP::Error &error) {
  108. auto box = PrePasswordErrorBox(
  109. error.type(),
  110. session,
  111. TextWithEntities{
  112. tr::lng_gift_transfer_password_about(tr::now),
  113. });
  114. if (box) {
  115. show->show(std::move(box));
  116. boxShown();
  117. return;
  118. }
  119. state->lifetime = session->api().cloudPassword().state(
  120. ) | rpl::take(
  121. 1
  122. ) | rpl::start_with_next([=](const Core::CloudPasswordState &pass) {
  123. auto fields = PasscodeBox::CloudFields::From(pass);
  124. fields.customTitle = tr::lng_gift_transfer_password_title();
  125. fields.customDescription
  126. = tr::lng_gift_transfer_password_description(tr::now);
  127. fields.customSubmitButton = tr::lng_passcode_submit();
  128. fields.customCheckCallback = crl::guard(parent, [=](
  129. const Core::CloudPasswordResult &result,
  130. QPointer<PasscodeBox> box) {
  131. using ExportUrl = MTPpayments_StarGiftWithdrawalUrl;
  132. session->api().request(
  133. MTPpayments_GetStarGiftWithdrawalUrl(
  134. Api::InputSavedStarGiftId(giftId),
  135. result.result)
  136. ).done([=](const ExportUrl &result) {
  137. UrlClickHandler::Open(qs(result.data().vurl()));
  138. wentToUrl();
  139. if (box) {
  140. box->closeBox();
  141. }
  142. }).fail([=](const MTP::Error &error) {
  143. const auto message = error.type();
  144. if (box && !box->handleCustomCheckError(message)) {
  145. show->showToast(message);
  146. }
  147. }).send();
  148. });
  149. show->show(Box<PasscodeBox>(session, fields));
  150. boxShown();
  151. });
  152. }).send();
  153. }
  154. [[nodiscard]] ExportOption MakeExportOption(
  155. not_null<Window::SessionController*> window,
  156. not_null<PeerListBox*> box,
  157. std::shared_ptr<Data::UniqueGift> gift,
  158. Data::SavedStarGiftId giftId,
  159. TimeId when) {
  160. struct State {
  161. bool exporting = false;
  162. };
  163. const auto state = std::make_shared<State>();
  164. const auto activate = [=] {
  165. const auto now = base::unixtime::now();
  166. const auto weak = Ui::MakeWeak(box);
  167. const auto left = (when > now) ? (when - now) : 0;
  168. const auto hours = left ? std::max((left + 1800) / 3600, 1) : 0;
  169. if (!hours) {
  170. window->show(Box(ConfirmExportBox, gift, [=](Fn<void()> close) {
  171. if (state->exporting) {
  172. return;
  173. }
  174. state->exporting = true;
  175. ExportOnBlockchain(window, box, gift, giftId, [=] {
  176. state->exporting = false;
  177. close();
  178. }, [=] {
  179. if (const auto strong = weak.data()) {
  180. strong->closeBox();
  181. }
  182. close();
  183. });
  184. }));
  185. return;
  186. }
  187. window->show(Ui::MakeInformBox({
  188. .text = tr::lng_gift_transfer_unlocks_about(
  189. lt_when,
  190. ((hours >= 24)
  191. ? tr::lng_gift_transfer_unlocks_when_days(
  192. lt_count,
  193. rpl::single((hours / 24) * 1.))
  194. : tr::lng_gift_transfer_unlocks_when_hours(
  195. lt_count,
  196. rpl::single(hours * 1.)))),
  197. .title = tr::lng_gift_transfer_unlocks_title(),
  198. }));
  199. };
  200. class ExportRow final : public PeerListRow {
  201. public:
  202. explicit ExportRow(TimeId when)
  203. : PeerListRow(Data::FakePeerIdForJustName("ton-export").value) {
  204. const auto now = base::unixtime::now();
  205. _available = (when <= now);
  206. if (const auto left = when - now; left > 0) {
  207. const auto hours = std::max((left + 1800) / 3600, 1);
  208. const auto days = hours / 24;
  209. setCustomStatus(days
  210. ? tr::lng_gift_transfer_unlocks_days(
  211. tr::now,
  212. lt_count,
  213. days)
  214. : tr::lng_gift_transfer_unlocks_hours(
  215. tr::now,
  216. lt_count,
  217. hours));
  218. }
  219. }
  220. QString generateName() override {
  221. return tr::lng_gift_transfer_via_blockchain(tr::now);
  222. }
  223. QString generateShortName() override {
  224. return generateName();
  225. }
  226. auto generatePaintUserpicCallback(bool forceRound)
  227. -> PaintRoundImageCallback override {
  228. return [=](
  229. Painter &p,
  230. int x,
  231. int y,
  232. int outerWidth,
  233. int size) mutable {
  234. Ui::EmptyUserpic::PaintCurrency(p, x, y, outerWidth, size);
  235. };
  236. }
  237. const style::PeerListItem &computeSt(
  238. const style::PeerListItem &st) const override {
  239. _st = st;
  240. _st.namePosition.setY(
  241. st::recentPeersSpecialName.namePosition.y());
  242. return _available ? _st : st;
  243. }
  244. private:
  245. mutable style::PeerListItem _st;
  246. bool _available = false;
  247. };
  248. class ExportController final : public PeerListController {
  249. public:
  250. ExportController(
  251. not_null<Main::Session*> session,
  252. TimeId when,
  253. Fn<void()> activate)
  254. : _session(session)
  255. , _when(when)
  256. , _activate(std::move(activate)) {
  257. }
  258. void prepare() override {
  259. delegate()->peerListAppendRow(
  260. std::make_unique<ExportRow>(_when));
  261. delegate()->peerListRefreshRows();
  262. }
  263. void loadMoreRows() override {
  264. }
  265. void rowClicked(not_null<PeerListRow*> row) override {
  266. _activate();
  267. }
  268. Main::Session &session() const override {
  269. return *_session;
  270. }
  271. private:
  272. const not_null<Main::Session*> _session;
  273. TimeId _when = 0;
  274. Fn<void()> _activate;
  275. };
  276. auto result = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
  277. const auto container = result.data();
  278. Ui::AddSkip(container);
  279. const auto delegate = container->lifetime().make_state<
  280. PeerListContentDelegateSimple
  281. >();
  282. const auto controller = container->lifetime().make_state<
  283. ExportController
  284. >(&window->session(), when, activate);
  285. controller->setStyleOverrides(&st::peerListSingleRow);
  286. const auto content = container->add(object_ptr<PeerListContent>(
  287. container,
  288. controller));
  289. delegate->setContent(content);
  290. controller->setDelegate(delegate);
  291. Ui::AddSkip(container);
  292. container->add(CreatePeerListSectionSubtitle(
  293. container,
  294. tr::lng_contacts_header()));
  295. const auto overrideKey = [=](int direction, int from, int to) {
  296. if (!content->isVisible()) {
  297. return false;
  298. } else if (direction > 0 && from < 0 && to >= 0) {
  299. if (content->hasSelection()) {
  300. const auto was = content->selectedIndex();
  301. const auto now = content->selectSkip(1).reallyMovedTo;
  302. if (was != now) {
  303. return true;
  304. }
  305. content->clearSelection();
  306. } else {
  307. content->selectSkip(1);
  308. return true;
  309. }
  310. } else if (direction < 0 && to < 0) {
  311. if (!content->hasSelection()) {
  312. content->selectLast();
  313. } else if (from >= 0 || content->hasSelection()) {
  314. content->selectSkip(-1);
  315. }
  316. }
  317. return false;
  318. };
  319. return {
  320. .content = std::move(result),
  321. .overrideKey = overrideKey,
  322. .activate = activate,
  323. };
  324. }
  325. Controller::Controller(
  326. not_null<Window::SessionController*> window,
  327. std::shared_ptr<Data::UniqueGift> gift,
  328. Data::SavedStarGiftId giftId,
  329. Fn<void(not_null<PeerData*>, Fn<void()>)> choose)
  330. : ContactsBoxController(&window->session())
  331. , _window(window)
  332. , _gift(std::move(gift))
  333. , _giftId(giftId)
  334. , _choose(std::move(choose)) {
  335. if (_gift->exportAt) {
  336. setStyleOverrides(&st::peerListSmallSkips);
  337. }
  338. }
  339. void Controller::init(not_null<PeerListBox*> box) {
  340. _box = box;
  341. if (const auto when = _gift->exportAt) {
  342. _exportOption = MakeExportOption(_window, box, _gift, _giftId, when);
  343. delegate()->peerListSetAboveWidget(std::move(_exportOption.content));
  344. delegate()->peerListRefreshRows();
  345. }
  346. }
  347. void Controller::noSearchSubmit() {
  348. if (const auto onstack = _exportOption.activate) {
  349. onstack();
  350. }
  351. }
  352. bool Controller::overrideKeyboardNavigation(
  353. int direction,
  354. int fromIndex,
  355. int toIndex) {
  356. return _exportOption.overrideKey
  357. && _exportOption.overrideKey(direction, fromIndex, toIndex);
  358. }
  359. void Controller::prepareViewHook() {
  360. delegate()->peerListSetTitle(tr::lng_gift_transfer_title(
  361. lt_name,
  362. rpl::single(UniqueGiftName(*_gift))));
  363. }
  364. std::unique_ptr<PeerListRow> Controller::createRow(
  365. not_null<UserData*> user) {
  366. if (user->isSelf()
  367. || user->isBot()
  368. || user->isServiceUser()
  369. || user->isInaccessible()) {
  370. return nullptr;
  371. }
  372. return ContactsBoxController::createRow(user);
  373. }
  374. void Controller::rowClicked(not_null<PeerListRow*> row) {
  375. _choose(row->peer(), [parentBox = _box] {
  376. if (const auto strong = parentBox.data()) {
  377. strong->closeBox();
  378. }
  379. });
  380. }
  381. void TransferGift(
  382. not_null<Window::SessionController*> window,
  383. not_null<PeerData*> to,
  384. std::shared_ptr<Data::UniqueGift> gift,
  385. Data::SavedStarGiftId savedId,
  386. Fn<void(Payments::CheckoutResult)> done) {
  387. Expects(to->isUser());
  388. const auto session = &window->session();
  389. const auto weak = base::make_weak(window);
  390. auto formDone = [=](
  391. Payments::CheckoutResult result,
  392. const MTPUpdates *updates) {
  393. done(result);
  394. if (result == Payments::CheckoutResult::Paid) {
  395. if (const auto strong = weak.get()) {
  396. strong->session().data().notifyGiftUpdate({
  397. .id = savedId,
  398. .action = Data::GiftUpdate::Action::Transfer,
  399. });
  400. Ui::ShowGiftTransferredToast(strong, to, *gift);
  401. }
  402. }
  403. };
  404. if (gift->starsForTransfer <= 0) {
  405. session->api().request(MTPpayments_TransferStarGift(
  406. Api::InputSavedStarGiftId(savedId),
  407. to->input
  408. )).done([=](const MTPUpdates &result) {
  409. session->api().applyUpdates(result);
  410. formDone(Payments::CheckoutResult::Paid, &result);
  411. }).fail([=](const MTP::Error &error) {
  412. formDone(Payments::CheckoutResult::Failed, nullptr);
  413. if (const auto strong = weak.get()) {
  414. strong->showToast(error.type());
  415. }
  416. }).send();
  417. return;
  418. }
  419. Ui::RequestStarsFormAndSubmit(
  420. window,
  421. MTP_inputInvoiceStarGiftTransfer(
  422. Api::InputSavedStarGiftId(savedId),
  423. to->input),
  424. std::move(formDone));
  425. }
  426. void ShowTransferToBox(
  427. not_null<Window::SessionController*> controller,
  428. not_null<PeerData*> peer,
  429. std::shared_ptr<Data::UniqueGift> gift,
  430. Data::SavedStarGiftId savedId,
  431. Fn<void()> closeParentBox) {
  432. const auto stars = gift->starsForTransfer;
  433. controller->show(Box([=](not_null<Ui::GenericBox*> box) {
  434. box->setTitle(tr::lng_gift_transfer_title(
  435. lt_name,
  436. rpl::single(UniqueGiftName(*gift))));
  437. auto transfer = (stars > 0)
  438. ? tr::lng_gift_transfer_button_for(
  439. lt_price,
  440. tr::lng_action_gift_for_stars(
  441. lt_count,
  442. rpl::single(stars * 1.)))
  443. : tr::lng_gift_transfer_button();
  444. struct State {
  445. bool sent = false;
  446. };
  447. const auto state = std::make_shared<State>();
  448. auto callback = [=] {
  449. if (state->sent) {
  450. return;
  451. }
  452. state->sent = true;
  453. const auto weak = Ui::MakeWeak(box);
  454. const auto done = [=](Payments::CheckoutResult result) {
  455. if (result == Payments::CheckoutResult::Cancelled) {
  456. closeParentBox();
  457. if (const auto strong = weak.data()) {
  458. strong->closeBox();
  459. }
  460. } else if (result != Payments::CheckoutResult::Paid) {
  461. state->sent = false;
  462. } else {
  463. if (savedId.isUser()) {
  464. controller->showPeerHistory(peer);
  465. }
  466. closeParentBox();
  467. if (const auto strong = weak.data()) {
  468. strong->closeBox();
  469. }
  470. }
  471. };
  472. TransferGift(controller, peer, gift, savedId, done);
  473. };
  474. Ui::ConfirmBox(box, {
  475. .text = (stars > 0)
  476. ? tr::lng_gift_transfer_sure_for(
  477. lt_name,
  478. rpl::single(Ui::Text::Bold(UniqueGiftName(*gift))),
  479. lt_recipient,
  480. rpl::single(Ui::Text::Bold(peer->shortName())),
  481. lt_price,
  482. tr::lng_action_gift_for_stars(
  483. lt_count,
  484. rpl::single(stars * 1.),
  485. Ui::Text::Bold),
  486. Ui::Text::WithEntities)
  487. : tr::lng_gift_transfer_sure(
  488. lt_name,
  489. rpl::single(Ui::Text::Bold(UniqueGiftName(*gift))),
  490. lt_recipient,
  491. rpl::single(Ui::Text::Bold(peer->shortName())),
  492. Ui::Text::WithEntities),
  493. .confirmed = std::move(callback),
  494. .confirmText = std::move(transfer),
  495. });
  496. }));
  497. }
  498. } // namespace
  499. void ShowTransferGiftBox(
  500. not_null<Window::SessionController*> window,
  501. std::shared_ptr<Data::UniqueGift> gift,
  502. Data::SavedStarGiftId savedId) {
  503. auto controller = std::make_unique<Controller>(
  504. window,
  505. gift,
  506. savedId,
  507. [=](not_null<PeerData*> peer, Fn<void()> done) {
  508. ShowTransferToBox(window, peer, gift, savedId, done);
  509. });
  510. const auto controllerRaw = controller.get();
  511. auto initBox = [=](not_null<PeerListBox*> box) {
  512. controllerRaw->init(box);
  513. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  514. box->noSearchSubmits() | rpl::start_with_next([=] {
  515. controllerRaw->noSearchSubmit();
  516. }, box->lifetime());
  517. };
  518. window->show(
  519. Box<PeerListBox>(std::move(controller), std::move(initBox)),
  520. Ui::LayerOption::KeepOther);
  521. }