api_chat_invite.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  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 "api/api_chat_invite.h"
  8. #include "apiwrap.h"
  9. #include "api/api_credits.h"
  10. #include "boxes/premium_limits_box.h"
  11. #include "core/application.h"
  12. #include "data/components/credits.h"
  13. #include "data/data_channel.h"
  14. #include "data/data_file_origin.h"
  15. #include "data/data_forum.h"
  16. #include "data/data_photo.h"
  17. #include "data/data_photo_media.h"
  18. #include "data/data_session.h"
  19. #include "data/data_user.h"
  20. #include "info/channel_statistics/boosts/giveaway/boost_badge.h"
  21. #include "info/profile/info_profile_badge.h"
  22. #include "lang/lang_keys.h"
  23. #include "main/main_session.h"
  24. #include "settings/settings_credits_graphics.h"
  25. #include "ui/boxes/confirm_box.h"
  26. #include "ui/controls/userpic_button.h"
  27. #include "ui/effects/credits_graphics.h"
  28. #include "ui/effects/premium_graphics.h"
  29. #include "ui/effects/premium_stars_colored.h"
  30. #include "ui/empty_userpic.h"
  31. #include "ui/layers/generic_box.h"
  32. #include "ui/painter.h"
  33. #include "ui/rect.h"
  34. #include "ui/text/text_utilities.h"
  35. #include "ui/toast/toast.h"
  36. #include "ui/vertical_list.h"
  37. #include "window/window_session_controller.h"
  38. #include "styles/style_boxes.h"
  39. #include "styles/style_chat_helpers.h"
  40. #include "styles/style_credits.h"
  41. #include "styles/style_info.h"
  42. #include "styles/style_layers.h"
  43. #include "styles/style_premium.h"
  44. namespace Api {
  45. namespace {
  46. void SubmitChatInvite(
  47. base::weak_ptr<Window::SessionController> weak,
  48. not_null<Main::Session*> session,
  49. const QString &hash,
  50. bool isGroup) {
  51. session->api().request(MTPmessages_ImportChatInvite(
  52. MTP_string(hash)
  53. )).done([=](const MTPUpdates &result) {
  54. session->api().applyUpdates(result);
  55. const auto strongController = weak.get();
  56. if (!strongController) {
  57. return;
  58. }
  59. strongController->hideLayer();
  60. const auto handleChats = [&](const MTPVector<MTPChat> &chats) {
  61. if (chats.v.isEmpty()) {
  62. return;
  63. }
  64. const auto peerId = chats.v[0].match([](const MTPDchat &data) {
  65. return peerFromChat(data.vid().v);
  66. }, [](const MTPDchannel &data) {
  67. return peerFromChannel(data.vid().v);
  68. }, [](auto&&) {
  69. return PeerId(0);
  70. });
  71. if (const auto peer = session->data().peerLoaded(peerId)) {
  72. // Shows in the primary window anyway.
  73. strongController->showPeerHistory(
  74. peer,
  75. Window::SectionShow::Way::Forward);
  76. }
  77. };
  78. result.match([&](const MTPDupdates &data) {
  79. handleChats(data.vchats());
  80. }, [&](const MTPDupdatesCombined &data) {
  81. handleChats(data.vchats());
  82. }, [&](auto &&) {
  83. LOG(("API Error: unexpected update cons %1 "
  84. "(ApiWrap::importChatInvite)").arg(result.type()));
  85. });
  86. }).fail([=](const MTP::Error &error) {
  87. const auto &type = error.type();
  88. const auto strongController = weak.get();
  89. if (!strongController) {
  90. return;
  91. } else if (type == u"CHANNELS_TOO_MUCH"_q) {
  92. strongController->show(
  93. Box(ChannelsLimitBox, &strongController->session()));
  94. return;
  95. }
  96. strongController->hideLayer();
  97. strongController->showToast([&] {
  98. if (type == u"INVITE_REQUEST_SENT"_q) {
  99. return isGroup
  100. ? tr::lng_group_request_sent(tr::now)
  101. : tr::lng_group_request_sent_channel(tr::now);
  102. } else if (type == u"USERS_TOO_MUCH"_q) {
  103. return tr::lng_group_invite_no_room(tr::now);
  104. } else {
  105. return tr::lng_group_invite_bad_link(tr::now);
  106. }
  107. }(), ApiWrap::kJoinErrorDuration);
  108. }).send();
  109. }
  110. void ConfirmSubscriptionBox(
  111. not_null<Ui::GenericBox*> box,
  112. not_null<Main::Session*> session,
  113. const QString &hash,
  114. const MTPDchatInvite *data) {
  115. box->setWidth(st::boxWideWidth);
  116. const auto amount = data->vsubscription_pricing()->data().vamount().v;
  117. const auto formId = data->vsubscription_form_id()->v;
  118. const auto name = qs(data->vtitle());
  119. const auto maybePhoto = session->data().processPhoto(data->vphoto());
  120. const auto photo = maybePhoto->isNull() ? nullptr : maybePhoto.get();
  121. struct State final {
  122. std::shared_ptr<Data::PhotoMedia> photoMedia;
  123. std::unique_ptr<Ui::EmptyUserpic> photoEmpty;
  124. QImage frame;
  125. std::optional<MTP::Sender> api;
  126. Ui::RpWidget* saveButton = nullptr;
  127. rpl::variable<bool> loading;
  128. };
  129. const auto state = box->lifetime().make_state<State>();
  130. const auto content = box->verticalLayout();
  131. Ui::AddSkip(content, st::confirmInvitePhotoTop);
  132. const auto userpicWrap = content->add(
  133. object_ptr<Ui::CenterWrap<>>(
  134. content,
  135. object_ptr<Ui::RpWidget>(content)));
  136. const auto userpic = userpicWrap->entity();
  137. const auto photoSize = st::confirmInvitePhotoSize;
  138. userpic->resize(Size(photoSize));
  139. const auto creditsIconSize = photoSize / 3;
  140. const auto creditsIconCallback =
  141. Ui::PaintOutlinedColoredCreditsIconCallback(
  142. creditsIconSize,
  143. 1.5);
  144. state->frame = QImage(
  145. Size(photoSize * style::DevicePixelRatio()),
  146. QImage::Format_ARGB32_Premultiplied);
  147. state->frame.setDevicePixelRatio(style::DevicePixelRatio());
  148. const auto options = Images::Option::RoundCircle;
  149. userpic->paintRequest(
  150. ) | rpl::start_with_next([=, small = Data::PhotoSize::Small] {
  151. state->frame.fill(Qt::transparent);
  152. {
  153. auto p = QPainter(&state->frame);
  154. if (state->photoMedia) {
  155. if (const auto image = state->photoMedia->image(small)) {
  156. p.drawPixmap(
  157. 0,
  158. 0,
  159. image->pix(Size(photoSize), { .options = options }));
  160. }
  161. } else if (state->photoEmpty) {
  162. state->photoEmpty->paintCircle(
  163. p,
  164. 0,
  165. 0,
  166. userpic->width(),
  167. photoSize);
  168. }
  169. if (creditsIconCallback) {
  170. p.translate(
  171. photoSize - creditsIconSize,
  172. photoSize - creditsIconSize);
  173. creditsIconCallback(p);
  174. }
  175. }
  176. auto p = QPainter(userpic);
  177. p.drawImage(0, 0, state->frame);
  178. }, userpicWrap->lifetime());
  179. userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
  180. if (photo) {
  181. state->photoMedia = photo->createMediaView();
  182. state->photoMedia->wanted(Data::PhotoSize::Small, Data::FileOrigin());
  183. if (!state->photoMedia->image(Data::PhotoSize::Small)) {
  184. session->downloaderTaskFinished(
  185. ) | rpl::start_with_next([=] {
  186. userpic->update();
  187. }, userpicWrap->entity()->lifetime());
  188. }
  189. } else {
  190. state->photoEmpty = std::make_unique<Ui::EmptyUserpic>(
  191. Ui::EmptyUserpic::UserpicColor(0),
  192. name);
  193. }
  194. Ui::AddSkip(content);
  195. Ui::AddSkip(content);
  196. Settings::AddMiniStars(
  197. content,
  198. Ui::CreateChild<Ui::RpWidget>(content),
  199. photoSize,
  200. box->width(),
  201. 2.);
  202. box->addRow(
  203. object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
  204. box,
  205. object_ptr<Ui::FlatLabel>(
  206. box,
  207. tr::lng_channel_invite_subscription_title(),
  208. st::inviteLinkSubscribeBoxTitle)));
  209. box->addRow(
  210. object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
  211. box,
  212. object_ptr<Ui::FlatLabel>(
  213. box,
  214. tr::lng_channel_invite_subscription_about(
  215. lt_channel,
  216. rpl::single(Ui::Text::Bold(name)),
  217. lt_price,
  218. tr::lng_credits_summary_options_credits(
  219. lt_count,
  220. rpl::single(amount) | tr::to_count(),
  221. Ui::Text::Bold),
  222. Ui::Text::WithEntities),
  223. st::inviteLinkSubscribeBoxAbout)));
  224. Ui::AddSkip(content);
  225. box->addRow(
  226. object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
  227. box,
  228. object_ptr<Ui::FlatLabel>(
  229. box,
  230. tr::lng_channel_invite_subscription_terms(
  231. lt_link,
  232. rpl::combine(
  233. tr::lng_paid_react_agree_link(),
  234. tr::lng_group_invite_subscription_about_url()
  235. ) | rpl::map([](const QString &text, const QString &url) {
  236. return Ui::Text::Link(text, url);
  237. }),
  238. Ui::Text::RichLangValue),
  239. st::inviteLinkSubscribeBoxTerms)));
  240. {
  241. const auto balance = Settings::AddBalanceWidget(
  242. content,
  243. session->credits().balanceValue(),
  244. true);
  245. session->credits().load(true);
  246. rpl::combine(
  247. balance->sizeValue(),
  248. content->sizeValue()
  249. ) | rpl::start_with_next([=](const QSize &, const QSize &) {
  250. balance->moveToRight(
  251. st::creditsHistoryRightSkip * 2,
  252. st::creditsHistoryRightSkip);
  253. balance->update();
  254. }, balance->lifetime());
  255. }
  256. const auto sendCredits = [=, weak = Ui::MakeWeak(box)] {
  257. const auto show = box->uiShow();
  258. const auto buttonWidth = state->saveButton
  259. ? state->saveButton->width()
  260. : 0;
  261. const auto finish = [=] {
  262. state->api = std::nullopt;
  263. state->loading.force_assign(false);
  264. if (const auto strong = weak.data()) {
  265. strong->closeBox();
  266. }
  267. };
  268. state->api->request(
  269. MTPpayments_SendStarsForm(
  270. MTP_long(formId),
  271. MTP_inputInvoiceChatInviteSubscription(MTP_string(hash)))
  272. ).done([=](const MTPpayments_PaymentResult &result) {
  273. result.match([&](const MTPDpayments_paymentResult &data) {
  274. session->api().applyUpdates(data.vupdates());
  275. }, [](const MTPDpayments_paymentVerificationNeeded &data) {
  276. });
  277. const auto refill = session->data().activeCreditsSubsRebuilder();
  278. const auto strong = weak.data();
  279. if (!strong) {
  280. return;
  281. }
  282. if (!refill) {
  283. return finish();
  284. }
  285. const auto api
  286. = strong->lifetime().make_state<Api::CreditsHistory>(
  287. session->user(),
  288. true,
  289. true);
  290. api->requestSubscriptions({}, [=](Data::CreditsStatusSlice d) {
  291. refill->fire(std::move(d));
  292. finish();
  293. });
  294. }).fail([=](const MTP::Error &error) {
  295. const auto id = error.type();
  296. if (weak) {
  297. state->api = std::nullopt;
  298. }
  299. show->showToast(id);
  300. state->loading.force_assign(false);
  301. }).send();
  302. if (state->saveButton) {
  303. state->saveButton->resizeToWidth(buttonWidth);
  304. }
  305. };
  306. auto confirmText = tr::lng_channel_invite_subscription_button();
  307. state->saveButton = box->addButton(std::move(confirmText), [=] {
  308. if (state->api) {
  309. return;
  310. }
  311. state->api.emplace(&session->mtp());
  312. state->loading.force_assign(true);
  313. const auto done = [=](Settings::SmallBalanceResult result) {
  314. if (result == Settings::SmallBalanceResult::Success
  315. || result == Settings::SmallBalanceResult::Already) {
  316. sendCredits();
  317. } else {
  318. state->api = std::nullopt;
  319. state->loading.force_assign(false);
  320. }
  321. };
  322. Settings::MaybeRequestBalanceIncrease(
  323. Main::MakeSessionShow(box->uiShow(), session),
  324. amount,
  325. Settings::SmallBalanceSubscription{ .name = name },
  326. done);
  327. });
  328. if (const auto saveButton = state->saveButton) {
  329. using namespace Info::Statistics;
  330. const auto loadingAnimation = InfiniteRadialAnimationWidget(
  331. saveButton,
  332. saveButton->height() / 2,
  333. &st::editStickerSetNameLoading);
  334. AddChildToWidgetCenter(saveButton, loadingAnimation);
  335. loadingAnimation->showOn(
  336. state->loading.value() | rpl::map(rpl::mappers::_1));
  337. }
  338. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  339. }
  340. } // namespace
  341. void CheckChatInvite(
  342. not_null<Window::SessionController*> controller,
  343. const QString &hash,
  344. ChannelData *invitePeekChannel,
  345. Fn<void()> loaded) {
  346. const auto session = &controller->session();
  347. const auto weak = base::make_weak(controller);
  348. session->api().checkChatInvite(hash, [=](const MTPChatInvite &result) {
  349. const auto strong = weak.get();
  350. if (!strong) {
  351. return;
  352. }
  353. if (loaded) {
  354. loaded();
  355. }
  356. Core::App().hideMediaView();
  357. const auto show = [&](not_null<PeerData*> chat) {
  358. const auto way = Window::SectionShow::Way::Forward;
  359. if (const auto forum = chat->forum()) {
  360. strong->showForum(forum, way);
  361. } else {
  362. strong->showPeerHistory(chat, way);
  363. }
  364. };
  365. result.match([=](const MTPDchatInvite &data) {
  366. const auto isGroup = !data.is_broadcast();
  367. const auto hasPricing = !!data.vsubscription_pricing();
  368. const auto canRefulfill = data.is_can_refulfill_subscription();
  369. if (hasPricing
  370. && !canRefulfill
  371. && !data.vsubscription_form_id()) {
  372. strong->uiShow()->showToast(
  373. tr::lng_confirm_phone_link_invalid(tr::now));
  374. return;
  375. }
  376. const auto box = (hasPricing && !canRefulfill)
  377. ? strong->show(Box(
  378. ConfirmSubscriptionBox,
  379. session,
  380. hash,
  381. &data))
  382. : strong->show(Box<ConfirmInviteBox>(
  383. session,
  384. data,
  385. invitePeekChannel,
  386. [=] { SubmitChatInvite(weak, session, hash, isGroup); }));
  387. if (invitePeekChannel) {
  388. box->boxClosing(
  389. ) | rpl::filter([=] {
  390. return !invitePeekChannel->amIn();
  391. }) | rpl::start_with_next([=] {
  392. if (const auto strong = weak.get()) {
  393. strong->clearSectionStack(Window::SectionShow(
  394. Window::SectionShow::Way::ClearStack,
  395. anim::type::normal,
  396. anim::activation::background));
  397. }
  398. }, box->lifetime());
  399. }
  400. }, [=](const MTPDchatInviteAlready &data) {
  401. if (const auto chat = session->data().processChat(data.vchat())) {
  402. if (const auto channel = chat->asChannel()) {
  403. channel->clearInvitePeek();
  404. }
  405. show(chat);
  406. }
  407. }, [=](const MTPDchatInvitePeek &data) {
  408. if (const auto chat = session->data().processChat(data.vchat())) {
  409. if (const auto channel = chat->asChannel()) {
  410. channel->setInvitePeek(hash, data.vexpires().v);
  411. show(chat);
  412. }
  413. }
  414. });
  415. }, [=](const MTP::Error &error) {
  416. if (error.code() != 400) {
  417. return;
  418. }
  419. Core::App().hideMediaView();
  420. if (const auto strong = weak.get()) {
  421. strong->show(Ui::MakeInformBox(tr::lng_group_invite_bad_link()));
  422. }
  423. });
  424. }
  425. } // namespace Api
  426. struct ConfirmInviteBox::Participant {
  427. not_null<UserData*> user;
  428. Ui::PeerUserpicView userpic;
  429. };
  430. ConfirmInviteBox::ConfirmInviteBox(
  431. QWidget*,
  432. not_null<Main::Session*> session,
  433. const MTPDchatInvite &data,
  434. ChannelData *invitePeekChannel,
  435. Fn<void()> submit)
  436. : ConfirmInviteBox(
  437. session,
  438. Parse(session, data),
  439. invitePeekChannel,
  440. std::move(submit)) {
  441. }
  442. ConfirmInviteBox::ConfirmInviteBox(
  443. not_null<Main::Session*> session,
  444. ChatInvite &&invite,
  445. ChannelData *invitePeekChannel,
  446. Fn<void()> submit)
  447. : _session(session)
  448. , _submit(std::move(submit))
  449. , _title(this, st::confirmInviteTitle)
  450. , _badge(std::make_unique<Info::Profile::Badge>(
  451. this,
  452. st::infoPeerBadge,
  453. _session,
  454. rpl::single(Info::Profile::Badge::Content{ BadgeForInvite(invite) }),
  455. nullptr,
  456. [=] { return false; }))
  457. , _status(this, st::confirmInviteStatus)
  458. , _about(this, st::confirmInviteAbout)
  459. , _aboutRequests(this, st::confirmInviteStatus)
  460. , _participants(std::move(invite.participants))
  461. , _isChannel(invite.isChannel && !invite.isMegagroup)
  462. , _requestApprove(invite.isRequestNeeded) {
  463. const auto count = invite.participantsCount;
  464. const auto status = [&] {
  465. return invitePeekChannel
  466. ? tr::lng_channel_invite_private(tr::now)
  467. : (!_participants.empty() && _participants.size() < count)
  468. ? tr::lng_group_invite_members(tr::now, lt_count, count)
  469. : (count > 0 && _isChannel)
  470. ? tr::lng_chat_status_subscribers(
  471. tr::now,
  472. lt_count_decimal,
  473. count)
  474. : (count > 0)
  475. ? tr::lng_chat_status_members(tr::now, lt_count_decimal, count)
  476. : _isChannel
  477. ? tr::lng_channel_status(tr::now)
  478. : tr::lng_group_status(tr::now);
  479. }();
  480. _title->setText(invite.title);
  481. _status->setText(status);
  482. if (!invite.about.isEmpty()) {
  483. _about->setText(invite.about);
  484. } else {
  485. _about.destroy();
  486. }
  487. if (_requestApprove) {
  488. _aboutRequests->setText(_isChannel
  489. ? tr::lng_group_request_about_channel(tr::now)
  490. : tr::lng_group_request_about(tr::now));
  491. } else {
  492. _aboutRequests.destroy();
  493. }
  494. if (invite.photo) {
  495. _photo = invite.photo->createMediaView();
  496. _photo->wanted(Data::PhotoSize::Small, Data::FileOrigin());
  497. if (!_photo->image(Data::PhotoSize::Small)) {
  498. _session->downloaderTaskFinished(
  499. ) | rpl::start_with_next([=] {
  500. update();
  501. }, lifetime());
  502. }
  503. } else {
  504. _photoEmpty = std::make_unique<Ui::EmptyUserpic>(
  505. Ui::EmptyUserpic::UserpicColor(0),
  506. invite.title);
  507. }
  508. }
  509. ConfirmInviteBox::~ConfirmInviteBox() = default;
  510. ConfirmInviteBox::ChatInvite ConfirmInviteBox::Parse(
  511. not_null<Main::Session*> session,
  512. const MTPDchatInvite &data) {
  513. auto participants = std::vector<Participant>();
  514. if (const auto list = data.vparticipants()) {
  515. participants.reserve(list->v.size());
  516. for (const auto &participant : list->v) {
  517. if (const auto user = session->data().processUser(participant)) {
  518. participants.push_back(Participant{ user });
  519. }
  520. }
  521. }
  522. const auto photo = session->data().processPhoto(data.vphoto());
  523. return {
  524. .title = qs(data.vtitle()),
  525. .about = data.vabout().value_or_empty(),
  526. .photo = (photo->isNull() ? nullptr : photo.get()),
  527. .participantsCount = data.vparticipants_count().v,
  528. .participants = std::move(participants),
  529. .isPublic = data.is_public(),
  530. .isChannel = data.is_channel(),
  531. .isMegagroup = data.is_megagroup(),
  532. .isBroadcast = data.is_broadcast(),
  533. .isRequestNeeded = data.is_request_needed(),
  534. .isFake = data.is_fake(),
  535. .isScam = data.is_scam(),
  536. .isVerified = data.is_verified(),
  537. };
  538. }
  539. [[nodiscard]] Info::Profile::BadgeType ConfirmInviteBox::BadgeForInvite(
  540. const ChatInvite &invite) {
  541. using Type = Info::Profile::BadgeType;
  542. return invite.isVerified
  543. ? Type::Verified
  544. : invite.isScam
  545. ? Type::Scam
  546. : invite.isFake
  547. ? Type::Fake
  548. : Type::None;
  549. }
  550. void ConfirmInviteBox::prepare() {
  551. addButton(
  552. (_requestApprove
  553. ? tr::lng_group_request_to_join()
  554. : _isChannel
  555. ? tr::lng_profile_join_channel()
  556. : tr::lng_profile_join_group()),
  557. _submit);
  558. addButton(tr::lng_cancel(), [=] { closeBox(); });
  559. while (_participants.size() > 4) {
  560. _participants.pop_back();
  561. }
  562. auto newHeight = st::confirmInviteStatusTop + _status->height() + st::boxPadding.bottom();
  563. if (!_participants.empty()) {
  564. int skip = (st::confirmInviteUsersWidth - 4 * st::confirmInviteUserPhotoSize) / 5;
  565. int padding = skip / 2;
  566. _userWidth = (st::confirmInviteUserPhotoSize + 2 * padding);
  567. int sumWidth = _participants.size() * _userWidth;
  568. int left = (st::boxWideWidth - sumWidth) / 2;
  569. for (const auto &participant : _participants) {
  570. auto name = new Ui::FlatLabel(this, st::confirmInviteUserName);
  571. name->resizeToWidth(st::confirmInviteUserPhotoSize + padding);
  572. name->setText(participant.user->firstName.isEmpty()
  573. ? participant.user->name()
  574. : participant.user->firstName);
  575. name->moveToLeft(left + (padding / 2), st::confirmInviteUserNameTop);
  576. left += _userWidth;
  577. }
  578. newHeight += st::confirmInviteUserHeight;
  579. }
  580. if (_about) {
  581. const auto padding = st::confirmInviteAboutPadding;
  582. _about->resizeToWidth(st::boxWideWidth - padding.left() - padding.right());
  583. newHeight += padding.top() + _about->height() + padding.bottom();
  584. }
  585. if (_aboutRequests) {
  586. const auto padding = st::confirmInviteAboutRequestsPadding;
  587. _aboutRequests->resizeToWidth(st::boxWideWidth - padding.left() - padding.right());
  588. newHeight += padding.top() + _aboutRequests->height() + padding.bottom();
  589. }
  590. setDimensions(st::boxWideWidth, newHeight);
  591. }
  592. void ConfirmInviteBox::resizeEvent(QResizeEvent *e) {
  593. BoxContent::resizeEvent(e);
  594. const auto padding = st::boxRowPadding;
  595. auto nameWidth = width() - padding.left() - padding.right();
  596. auto badgeWidth = 0;
  597. if (const auto widget = _badge->widget()) {
  598. badgeWidth = st::infoVerifiedCheckPosition.x() + widget->width();
  599. nameWidth -= badgeWidth;
  600. }
  601. _title->resizeToWidth(std::min(nameWidth, _title->textMaxWidth()));
  602. _title->moveToLeft(
  603. (width() - _title->width() - badgeWidth) / 2,
  604. st::confirmInviteTitleTop);
  605. const auto badgeLeft = _title->x() + _title->width();
  606. const auto badgeTop = _title->y();
  607. const auto badgeBottom = _title->y() + _title->height();
  608. _badge->move(badgeLeft, badgeTop, badgeBottom);
  609. _status->move(
  610. (width() - _status->width()) / 2,
  611. st::confirmInviteStatusTop);
  612. auto bottom = _status->y()
  613. + _status->height()
  614. + st::boxPadding.bottom()
  615. + (_participants.empty() ? 0 : st::confirmInviteUserHeight);
  616. if (_about) {
  617. const auto padding = st::confirmInviteAboutPadding;
  618. _about->move((width() - _about->width()) / 2, bottom + padding.top());
  619. bottom += padding.top() + _about->height() + padding.bottom();
  620. }
  621. if (_aboutRequests) {
  622. const auto padding = st::confirmInviteAboutRequestsPadding;
  623. _aboutRequests->move((width() - _aboutRequests->width()) / 2, bottom + padding.top());
  624. }
  625. }
  626. void ConfirmInviteBox::paintEvent(QPaintEvent *e) {
  627. BoxContent::paintEvent(e);
  628. Painter p(this);
  629. if (_photo) {
  630. if (const auto image = _photo->image(Data::PhotoSize::Small)) {
  631. const auto size = st::confirmInvitePhotoSize;
  632. p.drawPixmap(
  633. (width() - size) / 2,
  634. st::confirmInvitePhotoTop,
  635. image->pix(
  636. { size, size },
  637. { .options = Images::Option::RoundCircle }));
  638. }
  639. } else if (_photoEmpty) {
  640. _photoEmpty->paintCircle(
  641. p,
  642. (width() - st::confirmInvitePhotoSize) / 2,
  643. st::confirmInvitePhotoTop,
  644. width(),
  645. st::confirmInvitePhotoSize);
  646. }
  647. int sumWidth = _participants.size() * _userWidth;
  648. int left = (width() - sumWidth) / 2;
  649. for (auto &participant : _participants) {
  650. participant.user->paintUserpicLeft(
  651. p,
  652. participant.userpic,
  653. left + (_userWidth - st::confirmInviteUserPhotoSize) / 2,
  654. st::confirmInviteUserPhotoTop,
  655. width(),
  656. st::confirmInviteUserPhotoSize);
  657. left += _userWidth;
  658. }
  659. }