add_contact_box.cpp 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673
  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/add_contact_box.h"
  8. #include "lang/lang_keys.h"
  9. #include "base/call_delayed.h"
  10. #include "base/random.h"
  11. #include "ui/boxes/confirm_box.h"
  12. #include "boxes/abstract_box.h"
  13. #include "boxes/premium_limits_box.h"
  14. #include "boxes/peers/add_participants_box.h"
  15. #include "boxes/peers/edit_peer_common.h"
  16. #include "boxes/peers/edit_participant_box.h"
  17. #include "core/application.h"
  18. #include "core/core_settings.h"
  19. #include "chat_helpers/emoji_suggestions_widget.h"
  20. #include "countries/countries_instance.h" // Countries::ExtractPhoneCode.
  21. #include "history/history_item_reply_markup.h"
  22. #include "window/window_session_controller.h"
  23. #include "menu/menu_ttl.h"
  24. #include "ui/controls/userpic_button.h"
  25. #include "ui/widgets/checkbox.h"
  26. #include "ui/toast/toast.h"
  27. #include "ui/widgets/fields/input_field.h"
  28. #include "ui/widgets/fields/special_fields.h"
  29. #include "ui/widgets/popup_menu.h"
  30. #include "ui/text/format_values.h"
  31. #include "ui/text/text_utilities.h"
  32. #include "ui/painter.h"
  33. #include "data/data_channel.h"
  34. #include "data/data_chat.h"
  35. #include "data/data_user.h"
  36. #include "data/data_session.h"
  37. #include "data/data_changes.h"
  38. #include "apiwrap.h"
  39. #include "api/api_invite_links.h"
  40. #include "api/api_peer_photo.h"
  41. #include "api/api_self_destruct.h"
  42. #include "main/main_session.h"
  43. #include "styles/style_info.h"
  44. #include "styles/style_layers.h"
  45. #include "styles/style_menu_icons.h"
  46. #include <QtGui/QGuiApplication>
  47. #include <QtGui/QClipboard>
  48. namespace {
  49. bool IsValidPhone(QString phone) {
  50. phone = phone.replace(QRegularExpression(u"[^\\d]"_q), QString());
  51. return (phone.length() >= 8)
  52. || (phone == u"333"_q)
  53. || (phone.startsWith(u"42"_q)
  54. && (phone.length() == 2
  55. || phone.length() == 5
  56. || phone.length() == 6
  57. || phone == u"4242"_q));
  58. }
  59. void ChatCreateDone(
  60. not_null<Window::SessionNavigation*> navigation,
  61. QImage image,
  62. TimeId ttlPeriod,
  63. const MTPmessages_InvitedUsers &result,
  64. Fn<void(not_null<PeerData*>)> done) {
  65. const auto &data = result.data();
  66. navigation->session().api().applyUpdates(data.vupdates());
  67. const auto success = base::make_optional(&data.vupdates())
  68. | [](auto updates) -> std::optional<const QVector<MTPChat>*> {
  69. switch (updates->type()) {
  70. case mtpc_updates:
  71. return &updates->c_updates().vchats().v;
  72. case mtpc_updatesCombined:
  73. return &updates->c_updatesCombined().vchats().v;
  74. }
  75. LOG(("API Error: unexpected update cons %1 "
  76. "(GroupInfoBox::creationDone)").arg(updates->type()));
  77. return std::nullopt;
  78. }
  79. | [](auto chats) {
  80. return (!chats->empty()
  81. && chats->front().type() == mtpc_chat)
  82. ? base::make_optional(chats)
  83. : std::nullopt;
  84. }
  85. | [&](auto chats) {
  86. return navigation->session().data().chat(
  87. chats->front().c_chat().vid());
  88. }
  89. | [&](not_null<ChatData*> chat) {
  90. if (!image.isNull()) {
  91. chat->session().api().peerPhoto().upload(
  92. chat,
  93. { std::move(image) });
  94. }
  95. if (ttlPeriod) {
  96. chat->setMessagesTTL(ttlPeriod);
  97. }
  98. if (done) {
  99. done(chat);
  100. } else {
  101. const auto show = navigation->uiShow();
  102. navigation->showPeerHistory(chat);
  103. ChatInviteForbidden(
  104. show,
  105. chat,
  106. CollectForbiddenUsers(&chat->session(), result));
  107. }
  108. };
  109. if (!success) {
  110. LOG(("API Error: chat not found in updates "
  111. "(ContactsBox::creationDone)"));
  112. }
  113. }
  114. void MustBePublicDestroy(not_null<ChannelData*> channel) {
  115. const auto session = &channel->session();
  116. session->api().request(MTPchannels_DeleteChannel(
  117. channel->inputChannel
  118. )).done([=](const MTPUpdates &result) {
  119. session->api().applyUpdates(result);
  120. }).send();
  121. }
  122. void MustBePublicFailed(
  123. not_null<Window::SessionNavigation*> navigation,
  124. not_null<ChannelData*> channel) {
  125. const auto text = channel->isMegagroup()
  126. ? "Can't create a public group :("
  127. : "Can't create a public channel :(";
  128. navigation->showToast(text);
  129. MustBePublicDestroy(channel);
  130. }
  131. [[nodiscard]] Fn<void(not_null<PeerData*>)> WrapPeerDoneFromChannelDone(
  132. Fn<void(not_null<ChannelData*>)> channelDone) {
  133. if (!channelDone) {
  134. return nullptr;
  135. }
  136. return [=](not_null<PeerData*> peer) {
  137. if (const auto channel = peer->asChannel()) {
  138. const auto onstack = channelDone;
  139. onstack(channel);
  140. }
  141. };
  142. }
  143. } // namespace
  144. TextWithEntities PeerFloodErrorText(
  145. not_null<Main::Session*> session,
  146. PeerFloodType type) {
  147. const auto link = Ui::Text::Link(
  148. tr::lng_cant_more_info(tr::now),
  149. session->createInternalLinkFull(u"spambot"_q));
  150. return ((type == PeerFloodType::InviteGroup)
  151. ? tr::lng_cant_invite_not_contact
  152. : tr::lng_cant_send_to_not_contact)(
  153. tr::now,
  154. lt_more_info,
  155. link,
  156. Ui::Text::WithEntities);
  157. }
  158. void ShowAddParticipantsError(
  159. std::shared_ptr<Ui::Show> show,
  160. const QString &error,
  161. not_null<PeerData*> chat,
  162. not_null<UserData*> user) {
  163. ShowAddParticipantsError(
  164. std::move(show),
  165. error,
  166. chat,
  167. { .users = { 1, user } });
  168. }
  169. void ShowAddParticipantsError(
  170. std::shared_ptr<Ui::Show> show,
  171. const QString &error,
  172. not_null<PeerData*> chat,
  173. const ForbiddenInvites &forbidden) {
  174. if (error == u"USER_BOT"_q) {
  175. const auto channel = chat->asChannel();
  176. if ((forbidden.users.size() == 1)
  177. && forbidden.users.front()->isBot()
  178. && channel
  179. && !channel->isMegagroup()
  180. && channel->canAddAdmins()) {
  181. const auto makeAdmin = [=](Fn<void()> close) {
  182. const auto user = forbidden.users.front();
  183. const auto weak = std::make_shared<QPointer<EditAdminBox>>();
  184. const auto done = [=](auto&&...) {
  185. if (const auto strong = weak->data()) {
  186. strong->uiShow()->showToast(
  187. tr::lng_box_done(tr::now));
  188. strong->closeBox();
  189. }
  190. };
  191. const auto fail = [=] {
  192. if (const auto strong = weak->data()) {
  193. strong->closeBox();
  194. }
  195. };
  196. const auto saveCallback = SaveAdminCallback(
  197. show,
  198. channel,
  199. user,
  200. done,
  201. fail);
  202. auto box = Box<EditAdminBox>(
  203. channel,
  204. user,
  205. ChatAdminRightsInfo(),
  206. QString(),
  207. 0,
  208. nullptr);
  209. box->setSaveCallback(saveCallback);
  210. *weak = box.data();
  211. show->showBox(std::move(box));
  212. close();
  213. };
  214. show->showBox(
  215. Ui::MakeConfirmBox({
  216. .text = tr::lng_cant_invite_offer_admin(),
  217. .confirmed = makeAdmin,
  218. .confirmText = tr::lng_cant_invite_make_admin(),
  219. }),
  220. Ui::LayerOption::KeepOther);
  221. return;
  222. }
  223. }
  224. const auto hasBot = ranges::any_of(forbidden.users, &UserData::isBot);
  225. if (error == u"PEER_FLOOD"_q) {
  226. const auto type = (chat->isChat() || chat->isMegagroup())
  227. ? PeerFloodType::InviteGroup
  228. : PeerFloodType::InviteChannel;
  229. const auto text = PeerFloodErrorText(&chat->session(), type);
  230. Ui::show(Ui::MakeInformBox(text), Ui::LayerOption::KeepOther);
  231. return;
  232. } else if (error == u"USER_PRIVACY_RESTRICTED"_q) {
  233. ChatInviteForbidden(show, chat, forbidden);
  234. return;
  235. }
  236. const auto text = [&] {
  237. if (error == u"USER_BOT"_q) {
  238. return tr::lng_cant_invite_bot_to_channel(tr::now);
  239. } else if (error == u"USER_LEFT_CHAT"_q) {
  240. // Trying to return a user who has left.
  241. } else if (error == u"USER_KICKED"_q) {
  242. // Trying to return a user who was kicked by admin.
  243. return tr::lng_cant_invite_banned(tr::now);
  244. } else if (error == u"USER_NOT_MUTUAL_CONTACT"_q) {
  245. // Trying to return user who does not have me in contacts.
  246. return tr::lng_failed_add_not_mutual(tr::now);
  247. } else if (error == u"USER_ALREADY_PARTICIPANT"_q && hasBot) {
  248. return tr::lng_bot_already_in_group(tr::now);
  249. } else if (error == u"BOT_GROUPS_BLOCKED"_q) {
  250. return tr::lng_error_cant_add_bot(tr::now);
  251. } else if (error == u"YOU_BLOCKED_USER"_q) {
  252. return tr::lng_error_you_blocked_user(tr::now);
  253. } else if (error == u"CHAT_ADMIN_INVITE_REQUIRED"_q) {
  254. return tr::lng_error_add_admin_not_member(tr::now);
  255. } else if (error == u"USER_ADMIN_INVALID"_q) {
  256. return tr::lng_error_user_admin_invalid(tr::now);
  257. } else if (error == u"BOTS_TOO_MUCH"_q) {
  258. return (chat->isChannel()
  259. ? tr::lng_error_channel_bots_too_much
  260. : tr::lng_error_group_bots_too_much)(tr::now);
  261. }
  262. return tr::lng_failed_add_participant(tr::now);
  263. }();
  264. show->show(Ui::MakeInformBox(text), Ui::LayerOption::KeepOther);
  265. }
  266. AddContactBox::AddContactBox(
  267. QWidget*,
  268. not_null<Main::Session*> session)
  269. : AddContactBox(nullptr, session, QString(), QString(), QString()) {
  270. }
  271. AddContactBox::AddContactBox(
  272. QWidget*,
  273. not_null<Main::Session*> session,
  274. QString fname,
  275. QString lname,
  276. QString phone)
  277. : _session(session)
  278. , _first(this, st::defaultInputField, tr::lng_signup_firstname(), fname)
  279. , _last(this, st::defaultInputField, tr::lng_signup_lastname(), lname)
  280. , _phone(
  281. this,
  282. st::defaultInputField,
  283. tr::lng_contact_phone(),
  284. Countries::ExtractPhoneCode(session->user()->phone()),
  285. phone,
  286. [](const QString &s) { return Countries::Groups(s); })
  287. , _invertOrder(langFirstNameGoesSecond()) {
  288. if (!phone.isEmpty()) {
  289. _phone->setDisabled(true);
  290. }
  291. }
  292. void AddContactBox::prepare() {
  293. if (_invertOrder) {
  294. setTabOrder(_last, _first);
  295. }
  296. const auto readyToAdd = !_phone->getLastText().isEmpty()
  297. && (!_first->getLastText().isEmpty()
  298. || !_last->getLastText().isEmpty());
  299. setTitle(readyToAdd
  300. ? tr::lng_confirm_contact_data()
  301. : tr::lng_enter_contact_data());
  302. updateButtons();
  303. const auto submitted = [=] { submit(); };
  304. _first->submits(
  305. ) | rpl::start_with_next(submitted, _first->lifetime());
  306. _last->submits(
  307. ) | rpl::start_with_next(submitted, _last->lifetime());
  308. connect(_phone, &Ui::PhoneInput::submitted, [=] { submit(); });
  309. setDimensions(
  310. st::boxWideWidth,
  311. st::contactPadding.top()
  312. + _first->height()
  313. + st::contactSkip
  314. + _last->height()
  315. + st::contactPhoneSkip
  316. + _phone->height()
  317. + st::contactPadding.bottom()
  318. + st::boxPadding.bottom());
  319. }
  320. void AddContactBox::setInnerFocus() {
  321. if ((_first->getLastText().isEmpty() && _last->getLastText().isEmpty())
  322. || !_phone->isEnabled()) {
  323. (_invertOrder ? _last : _first)->setFocusFast();
  324. _phone->finishAnimating();
  325. } else {
  326. _phone->setFocusFast();
  327. }
  328. }
  329. void AddContactBox::paintEvent(QPaintEvent *e) {
  330. BoxContent::paintEvent(e);
  331. auto p = QPainter(this);
  332. if (_retrying) {
  333. p.setPen(st::boxTextFg);
  334. p.setFont(st::boxTextFont);
  335. const auto textHeight = height()
  336. - st::contactPadding.top()
  337. - st::contactPadding.bottom()
  338. - st::boxPadding.bottom();
  339. p.drawText(
  340. QRect(
  341. st::boxPadding.left(),
  342. st::contactPadding.top(),
  343. width() - st::boxPadding.left() - st::boxPadding.right(),
  344. textHeight),
  345. tr::lng_contact_not_joined(tr::now, lt_name, _sentName),
  346. style::al_topleft);
  347. } else {
  348. st::contactUserIcon.paint(
  349. p,
  350. st::boxPadding.left() + st::contactIconPosition.x(),
  351. _first->y() + st::contactIconPosition.y(),
  352. width());
  353. st::contactPhoneIcon.paint(
  354. p,
  355. st::boxPadding.left() + st::contactIconPosition.x(),
  356. _phone->y() + st::contactIconPosition.y(),
  357. width());
  358. }
  359. }
  360. void AddContactBox::resizeEvent(QResizeEvent *e) {
  361. BoxContent::resizeEvent(e);
  362. _first->resize(
  363. width()
  364. - st::boxPadding.left()
  365. - st::contactPadding.left()
  366. - st::boxPadding.right(),
  367. _first->height());
  368. _last->resize(_first->width(), _last->height());
  369. _phone->resize(_first->width(), _last->height());
  370. const auto left = st::boxPadding.left() + st::contactPadding.left();
  371. const auto &firstRow = _invertOrder ? _last : _first;
  372. const auto &secondRow = _invertOrder ? _first : _last;
  373. const auto &thirdRow = _phone;
  374. firstRow->moveToLeft(left, st::contactPadding.top());
  375. secondRow->moveToLeft(
  376. left,
  377. firstRow->y() + firstRow->height() + st::contactSkip);
  378. thirdRow->moveToLeft(
  379. left,
  380. secondRow->y() + secondRow->height() + st::contactPhoneSkip);
  381. }
  382. void AddContactBox::submit() {
  383. if (_first->hasFocus()) {
  384. _last->setFocus();
  385. } else if (_last->hasFocus()) {
  386. if (_phone->isEnabled()) {
  387. _phone->setFocus();
  388. } else {
  389. save();
  390. }
  391. } else if (_phone->hasFocus()) {
  392. save();
  393. }
  394. }
  395. void AddContactBox::save() {
  396. if (_addRequest) {
  397. return;
  398. }
  399. auto firstName = TextUtilities::PrepareForSending(
  400. _first->getLastText());
  401. auto lastName = TextUtilities::PrepareForSending(
  402. _last->getLastText());
  403. const auto phone = _phone->getLastText().trimmed();
  404. if (firstName.isEmpty() && lastName.isEmpty()) {
  405. if (_invertOrder) {
  406. _last->setFocus();
  407. _last->showError();
  408. } else {
  409. _first->setFocus();
  410. _first->showError();
  411. }
  412. return;
  413. } else if (!IsValidPhone(phone)) {
  414. _phone->setFocus();
  415. _phone->showError();
  416. return;
  417. }
  418. if (firstName.isEmpty()) {
  419. firstName = lastName;
  420. lastName = QString();
  421. }
  422. const auto weak = Ui::MakeWeak(this);
  423. const auto session = _session;
  424. _sentName = firstName;
  425. _contactId = base::RandomValue<uint64>();
  426. _addRequest = _session->api().request(MTPcontacts_ImportContacts(
  427. MTP_vector<MTPInputContact>(
  428. 1,
  429. MTP_inputPhoneContact(
  430. MTP_long(_contactId),
  431. MTP_string(phone),
  432. MTP_string(firstName),
  433. MTP_string(lastName)))
  434. )).done(crl::guard(weak, [=](
  435. const MTPcontacts_ImportedContacts &result) {
  436. const auto &data = result.data();
  437. session->data().processUsers(data.vusers());
  438. if (!weak) {
  439. return;
  440. }
  441. const auto extractUser = [&](const MTPImportedContact &data) {
  442. return data.match([&](const MTPDimportedContact &data) {
  443. return (data.vclient_id().v == _contactId)
  444. ? session->data().userLoaded(data.vuser_id())
  445. : nullptr;
  446. });
  447. };
  448. const auto &list = data.vimported().v;
  449. const auto user = list.isEmpty()
  450. ? nullptr
  451. : extractUser(list.front());
  452. if (user) {
  453. if (user->isContact() || user->session().supportMode()) {
  454. if (const auto window = user->session().tryResolveWindow()) {
  455. window->showPeerHistory(user);
  456. }
  457. }
  458. if (weak) { // showPeerHistory could close the box.
  459. getDelegate()->hideLayer();
  460. }
  461. } else if (isBoxShown()) {
  462. hideChildren();
  463. _retrying = true;
  464. updateButtons();
  465. update();
  466. }
  467. })).send();
  468. }
  469. void AddContactBox::retry() {
  470. _addRequest = 0;
  471. _contactId = 0;
  472. showChildren();
  473. _retrying = false;
  474. updateButtons();
  475. _first->setText(QString());
  476. _last->setText(QString());
  477. _phone->clearText();
  478. _phone->setDisabled(false);
  479. _first->setFocus();
  480. update();
  481. }
  482. void AddContactBox::updateButtons() {
  483. clearButtons();
  484. if (_retrying) {
  485. addButton(tr::lng_try_other_contact(), [=] { retry(); });
  486. } else {
  487. addButton(tr::lng_add_contact(), [=] { save(); });
  488. addButton(tr::lng_cancel(), [=] { closeBox(); });
  489. }
  490. }
  491. GroupInfoBox::GroupInfoBox(
  492. QWidget*,
  493. not_null<Window::SessionNavigation*> navigation,
  494. Type type,
  495. const QString &title,
  496. Fn<void(not_null<ChannelData*>)> channelDone)
  497. : _navigation(navigation)
  498. , _api(&_navigation->session().mtp())
  499. , _type(type)
  500. , _initialTitle(title)
  501. , _done(WrapPeerDoneFromChannelDone(std::move(channelDone))) {
  502. }
  503. GroupInfoBox::GroupInfoBox(
  504. QWidget*,
  505. not_null<Window::SessionNavigation*> navigation,
  506. not_null<UserData*> bot,
  507. RequestPeerQuery query,
  508. Fn<void(not_null<PeerData*>)> done)
  509. : _navigation(navigation)
  510. , _api(&_navigation->session().mtp())
  511. , _type((query.type == RequestPeerQuery::Type::Broadcast)
  512. ? Type::Channel
  513. : (query.groupIsForum == RequestPeerQuery::Restriction::Yes)
  514. ? Type::Forum
  515. : (query.hasUsername == RequestPeerQuery::Restriction::Yes)
  516. ? Type::Megagroup
  517. : Type::Group)
  518. , _mustBePublic(query.hasUsername == RequestPeerQuery::Restriction::Yes)
  519. , _canAddBot(query.isBotParticipant ? bot.get() : nullptr)
  520. , _done(std::move(done)) {
  521. }
  522. void GroupInfoBox::prepare() {
  523. setMouseTracking(true);
  524. _photo.create(
  525. this,
  526. &_navigation->parentController()->window(),
  527. Ui::UserpicButton::Role::ChoosePhoto,
  528. st::defaultUserpicButton,
  529. (_type == Type::Forum));
  530. _photo->showCustomOnChosen();
  531. _title.create(
  532. this,
  533. st::defaultInputField,
  534. (_type == Type::Channel
  535. ? tr::lng_dlg_new_channel_name
  536. : tr::lng_dlg_new_group_name)(),
  537. _initialTitle);
  538. _title->setMaxLength(Ui::EditPeer::kMaxGroupChannelTitle);
  539. _title->setInstantReplaces(Ui::InstantReplaces::Default());
  540. _title->setInstantReplacesEnabled(
  541. Core::App().settings().replaceEmojiValue());
  542. Ui::Emoji::SuggestionsController::Init(
  543. getDelegate()->outerContainer(),
  544. _title,
  545. &_navigation->session());
  546. if (_type != Type::Group) {
  547. _description.create(
  548. this,
  549. st::newGroupDescription,
  550. Ui::InputField::Mode::MultiLine,
  551. tr::lng_create_group_description());
  552. _description->show();
  553. _description->setMaxLength(Ui::EditPeer::kMaxChannelDescription);
  554. _description->setInstantReplaces(Ui::InstantReplaces::Default());
  555. _description->setInstantReplacesEnabled(
  556. Core::App().settings().replaceEmojiValue());
  557. _description->setSubmitSettings(
  558. Core::App().settings().sendSubmitWay());
  559. _description->heightChanges(
  560. ) | rpl::start_with_next([=] {
  561. descriptionResized();
  562. }, _description->lifetime());
  563. _description->submits(
  564. ) | rpl::start_with_next([=] { submit(); }, _description->lifetime());
  565. _description->cancelled(
  566. ) | rpl::start_with_next([=] {
  567. closeBox();
  568. }, _description->lifetime());
  569. Ui::Emoji::SuggestionsController::Init(
  570. getDelegate()->outerContainer(),
  571. _description,
  572. &_navigation->session());
  573. }
  574. _title->submits(
  575. ) | rpl::start_with_next([=] { submitName(); }, _title->lifetime());
  576. addButton(
  577. ((_type != Type::Group || _canAddBot)
  578. ? tr::lng_create_group_create()
  579. : tr::lng_create_group_next()),
  580. [=] { submit(); });
  581. addButton(tr::lng_cancel(), [this] { closeBox(); });
  582. if (_type == Type::Group) {
  583. _navigation->session().api().selfDestruct().reload();
  584. const auto top = addTopButton(st::infoTopBarMenu);
  585. const auto menu
  586. = top->lifetime().make_state<base::unique_qptr<Ui::PopupMenu>>();
  587. top->setClickedCallback([=] {
  588. *menu = base::make_unique_q<Ui::PopupMenu>(
  589. top,
  590. st::popupMenuWithIcons);
  591. const auto ttl = ttlPeriod();
  592. const auto text = tr::lng_manage_messages_ttl_menu(tr::now)
  593. + (ttl ? ('\t' + Ui::FormatTTLTiny(ttl)) : QString());
  594. (*menu)->addAction(
  595. text,
  596. [=, show = uiShow()] {
  597. show->showBox(Box(TTLMenu::TTLBox, TTLMenu::Args{
  598. .show = show,
  599. .startTtl = ttlPeriod(),
  600. .about = nullptr,
  601. .callback = crl::guard(this, [=](
  602. TimeId t,
  603. Fn<void()> close) {
  604. _ttlPeriod = t;
  605. _ttlPeriodOverridden = true;
  606. close();
  607. }),
  608. }));
  609. }, &st::menuIconTTL);
  610. (*menu)->popup(QCursor::pos());
  611. return true;
  612. });
  613. }
  614. updateMaxHeight();
  615. }
  616. void GroupInfoBox::setInnerFocus() {
  617. _title->setFocusFast();
  618. }
  619. void GroupInfoBox::resizeEvent(QResizeEvent *e) {
  620. BoxContent::resizeEvent(e);
  621. _photo->moveToLeft(
  622. st::boxPadding.left() + st::newGroupInfoPadding.left(),
  623. st::boxPadding.top() + st::newGroupInfoPadding.top());
  624. const auto nameLeft = st::defaultUserpicButton.size.width()
  625. + st::newGroupNamePosition.x();
  626. _title->resize(
  627. width()
  628. - st::boxPadding.left()
  629. - st::newGroupInfoPadding.left()
  630. - st::boxPadding.right()
  631. - nameLeft,
  632. _title->height());
  633. _title->moveToLeft(
  634. st::boxPadding.left() + st::newGroupInfoPadding.left() + nameLeft,
  635. st::boxPadding.top()
  636. + st::newGroupInfoPadding.top()
  637. + st::newGroupNamePosition.y());
  638. if (_description) {
  639. _description->resize(
  640. width()
  641. - st::boxPadding.left()
  642. - st::newGroupInfoPadding.left()
  643. - st::boxPadding.right(),
  644. _description->height());
  645. const auto descriptionLeft = st::boxPadding.left()
  646. + st::newGroupInfoPadding.left();
  647. const auto descriptionTop = st::boxPadding.top()
  648. + st::newGroupInfoPadding.top()
  649. + st::defaultUserpicButton.size.height()
  650. + st::newGroupDescriptionPadding.top();
  651. _description->moveToLeft(descriptionLeft, descriptionTop);
  652. }
  653. }
  654. void GroupInfoBox::submitName() {
  655. if (_title->getLastText().trimmed().isEmpty()) {
  656. _title->setFocus();
  657. _title->showError();
  658. } else if (_description) {
  659. _description->setFocus();
  660. } else {
  661. submit();
  662. }
  663. }
  664. TimeId GroupInfoBox::ttlPeriod() const {
  665. return _ttlPeriodOverridden
  666. ? _ttlPeriod
  667. : _navigation->session().api().selfDestruct()
  668. .periodDefaultHistoryTTLCurrent();
  669. }
  670. void GroupInfoBox::createGroup(
  671. QPointer<Ui::BoxContent> selectUsersBox,
  672. const QString &title,
  673. const std::vector<not_null<PeerData*>> &users) {
  674. if (_creationRequestId) {
  675. return;
  676. }
  677. using TLUsers = MTPInputUser;
  678. auto inputs = QVector<TLUsers>();
  679. inputs.reserve(users.size());
  680. for (auto peer : users) {
  681. auto user = peer->asUser();
  682. Assert(user != nullptr);
  683. if (!user->isSelf()) {
  684. inputs.push_back(user->inputUser);
  685. }
  686. }
  687. _creationRequestId = _api.request(MTPmessages_CreateChat(
  688. MTP_flags(MTPmessages_CreateChat::Flag::f_ttl_period),
  689. MTP_vector<TLUsers>(inputs),
  690. MTP_string(title),
  691. MTP_int(ttlPeriod())
  692. )).done([=](const MTPmessages_InvitedUsers &result) {
  693. auto image = _photo->takeResultImage();
  694. const auto period = ttlPeriod();
  695. const auto navigation = _navigation;
  696. const auto done = _done;
  697. getDelegate()->hideLayer(); // Destroys 'this'.
  698. ChatCreateDone(navigation, std::move(image), period, result, done);
  699. }).fail([=](const MTP::Error &error) {
  700. const auto &type = error.type();
  701. _creationRequestId = 0;
  702. const auto controller = _navigation->parentController();
  703. if (type == u"NO_CHAT_TITLE"_q) {
  704. const auto weak = Ui::MakeWeak(this);
  705. if (const auto strong = selectUsersBox.data()) {
  706. strong->closeBox();
  707. }
  708. if (weak) {
  709. _title->showError();
  710. }
  711. } else if (type == u"USERS_TOO_FEW"_q) {
  712. controller->show(
  713. Ui::MakeInformBox(tr::lng_cant_invite_privacy()));
  714. } else if (type == u"PEER_FLOOD"_q) {
  715. controller->show(Ui::MakeInformBox(
  716. PeerFloodErrorText(
  717. &_navigation->session(),
  718. PeerFloodType::InviteGroup)));
  719. } else if (type == u"USER_RESTRICTED"_q) {
  720. controller->show(Ui::MakeInformBox(tr::lng_cant_do_this()));
  721. }
  722. }).send();
  723. }
  724. void GroupInfoBox::submit() {
  725. if (_creationRequestId || _creatingInviteLink) {
  726. return;
  727. }
  728. auto title = TextUtilities::PrepareForSending(_title->getLastText());
  729. auto description = _description
  730. ? TextUtilities::PrepareForSending(
  731. _description->getLastText(),
  732. TextUtilities::PrepareTextOption::CheckLinks)
  733. : QString();
  734. if (title.isEmpty()) {
  735. _title->setFocus();
  736. _title->showError();
  737. return;
  738. }
  739. if (_type != Type::Group) {
  740. createChannel(title, description);
  741. } else if (_canAddBot) {
  742. createGroup(nullptr, title, { not_null<PeerData*>(_canAddBot) });
  743. } else {
  744. auto initBox = [title, weak = Ui::MakeWeak(this)](
  745. not_null<PeerListBox*> box) {
  746. auto create = [box, title, weak] {
  747. if (const auto strong = weak.data()) {
  748. strong->createGroup(
  749. box.get(),
  750. title,
  751. box->collectSelectedRows());
  752. }
  753. };
  754. box->addButton(tr::lng_create_group_create(), std::move(create));
  755. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  756. };
  757. Ui::show(
  758. Box<PeerListBox>(
  759. std::make_unique<AddParticipantsBoxController>(
  760. &_navigation->session()),
  761. std::move(initBox)),
  762. Ui::LayerOption::KeepOther);
  763. }
  764. }
  765. void GroupInfoBox::createChannel(
  766. const QString &title,
  767. const QString &description) {
  768. Expects(!_creationRequestId);
  769. using Flag = MTPchannels_CreateChannel::Flag;
  770. const auto flags = Flag()
  771. | ((_type == Type::Megagroup || _type == Type::Forum)
  772. ? Flag::f_megagroup
  773. : Flag::f_broadcast)
  774. | ((_type == Type::Forum) ? Flag::f_forum : Flag())
  775. | ((_type == Type::Megagroup)
  776. ? MTPchannels_CreateChannel::Flag::f_ttl_period
  777. : MTPchannels_CreateChannel::Flags(0));
  778. const auto ttl = ttlPeriod();
  779. _creationRequestId = _api.request(MTPchannels_CreateChannel(
  780. MTP_flags(flags),
  781. MTP_string(title),
  782. MTP_string(description),
  783. MTPInputGeoPoint(), // geo_point
  784. MTPstring(), // address
  785. MTP_int((_type == Type::Megagroup) ? ttl : 0)
  786. )).done([=](const MTPUpdates &result) {
  787. _navigation->session().api().applyUpdates(result);
  788. const auto success = base::make_optional(&result)
  789. | [](auto updates) -> std::optional<const QVector<MTPChat>*> {
  790. switch (updates->type()) {
  791. case mtpc_updates:
  792. return &updates->c_updates().vchats().v;
  793. case mtpc_updatesCombined:
  794. return &updates->c_updatesCombined().vchats().v;
  795. }
  796. LOG(("API Error: unexpected update cons %1 "
  797. "(GroupInfoBox::createChannel)").arg(updates->type()));
  798. return std::nullopt;
  799. }
  800. | [](auto chats) {
  801. return (!chats->empty()
  802. && chats->front().type() == mtpc_channel)
  803. ? base::make_optional(chats)
  804. : std::nullopt;
  805. }
  806. | [&](auto chats) {
  807. return _navigation->session().data().channel(
  808. chats->front().c_channel().vid());
  809. }
  810. | [&](not_null<ChannelData*> channel) {
  811. auto image = _photo->takeResultImage();
  812. if (!image.isNull()) {
  813. channel->session().api().peerPhoto().upload(
  814. channel,
  815. { std::move(image) });
  816. }
  817. if (ttl && channel->isMegagroup()) {
  818. channel->setMessagesTTL(ttl);
  819. }
  820. channel->session().api().requestFullPeer(channel);
  821. _createdChannel = channel;
  822. checkInviteLink();
  823. };
  824. if (!success) {
  825. LOG(("API Error: channel not found in updates "
  826. "(GroupInfoBox::creationDone)"));
  827. closeBox();
  828. }
  829. }).fail([this](const MTP::Error &error) {
  830. const auto &type = error.type();
  831. _creationRequestId = 0;
  832. const auto controller = _navigation->parentController();
  833. if (type == u"NO_CHAT_TITLE"_q) {
  834. _title->setFocus();
  835. _title->showError();
  836. } else if (type == u"USER_RESTRICTED"_q) {
  837. controller->show(
  838. Ui::MakeInformBox(tr::lng_cant_do_this()),
  839. Ui::LayerOption::CloseOther);
  840. } else if (type == u"CHANNELS_TOO_MUCH"_q) {
  841. controller->show(
  842. Box(ChannelsLimitBox, &controller->session()),
  843. Ui::LayerOption::CloseOther); // TODO
  844. }
  845. }).send();
  846. }
  847. void GroupInfoBox::checkInviteLink() {
  848. Expects(_createdChannel != nullptr);
  849. if (!_createdChannel->inviteLink().isEmpty()) {
  850. channelReady();
  851. } else if (_createdChannel->isFullLoaded() && !_creatingInviteLink) {
  852. _creatingInviteLink = true;
  853. _createdChannel->session().api().inviteLinks().create({
  854. _createdChannel,
  855. crl::guard(this, [=](auto&&) { channelReady(); }),
  856. });
  857. } else {
  858. _createdChannel->session().changes().peerUpdates(
  859. _createdChannel,
  860. Data::PeerUpdate::Flag::FullInfo
  861. ) | rpl::take(1) | rpl::start_with_next([=] {
  862. checkInviteLink();
  863. }, lifetime());
  864. }
  865. }
  866. void GroupInfoBox::channelReady() {
  867. if (_done && !_mustBePublic) {
  868. const auto callback = _done;
  869. const auto argument = _createdChannel;
  870. closeBox();
  871. callback(argument);
  872. } else {
  873. _navigation->parentController()->show(
  874. Box<SetupChannelBox>(
  875. _navigation,
  876. _createdChannel,
  877. _mustBePublic,
  878. _done),
  879. Ui::LayerOption::CloseOther);
  880. }
  881. }
  882. void GroupInfoBox::descriptionResized() {
  883. updateMaxHeight();
  884. update();
  885. }
  886. void GroupInfoBox::updateMaxHeight() {
  887. auto newHeight = st::boxPadding.top()
  888. + st::newGroupInfoPadding.top()
  889. + st::defaultUserpicButton.size.height()
  890. + st::boxPadding.bottom()
  891. + st::newGroupInfoPadding.bottom();
  892. if (_description) {
  893. newHeight += st::newGroupDescriptionPadding.top()
  894. + _description->height()
  895. + st::newGroupDescriptionPadding.bottom();
  896. }
  897. setDimensions(st::boxWideWidth, newHeight);
  898. }
  899. SetupChannelBox::SetupChannelBox(
  900. QWidget*,
  901. not_null<Window::SessionNavigation*> navigation,
  902. not_null<ChannelData*> channel,
  903. bool mustBePublic,
  904. Fn<void(not_null<PeerData*>)> done)
  905. : _navigation(navigation)
  906. , _channel(channel)
  907. , _api(&_channel->session().mtp())
  908. , _mustBePublic(mustBePublic)
  909. , _done(std::move(done))
  910. , _privacyGroup(
  911. std::make_shared<Ui::RadioenumGroup<Privacy>>(Privacy::Public))
  912. , _public(
  913. this,
  914. _privacyGroup,
  915. Privacy::Public,
  916. (channel->isMegagroup()
  917. ? tr::lng_create_public_group_title
  918. : tr::lng_create_public_channel_title)(tr::now),
  919. st::defaultBoxCheckbox)
  920. , _private(
  921. this,
  922. _privacyGroup,
  923. Privacy::Private,
  924. (channel->isMegagroup()
  925. ? tr::lng_create_private_group_title
  926. : tr::lng_create_private_channel_title)(tr::now),
  927. st::defaultBoxCheckbox)
  928. , _aboutPublicWidth(st::boxWideWidth
  929. - st::boxPadding.left()
  930. - st::defaultBox.buttonPadding.right()
  931. - st::newGroupPadding.left()
  932. - st::defaultRadio.diameter
  933. - st::defaultBoxCheckbox.textPosition.x())
  934. , _aboutPublic(
  935. st::defaultTextStyle,
  936. (channel->isMegagroup()
  937. ? tr::lng_create_public_group_about
  938. : tr::lng_create_public_channel_about)(tr::now),
  939. kDefaultTextOptions,
  940. _aboutPublicWidth)
  941. , _aboutPrivate(
  942. st::defaultTextStyle,
  943. (channel->isMegagroup()
  944. ? tr::lng_create_private_group_about
  945. : tr::lng_create_private_channel_about)(tr::now),
  946. kDefaultTextOptions,
  947. _aboutPublicWidth)
  948. , _link(
  949. this,
  950. st::setupChannelLink,
  951. nullptr,
  952. channel->username(),
  953. channel->session().createInternalLink(QString()))
  954. , _checkTimer([=] { check(); }) {
  955. if (_mustBePublic) {
  956. _public.destroy();
  957. _private.destroy();
  958. }
  959. }
  960. void SetupChannelBox::prepare() {
  961. _aboutPublicHeight = _aboutPublic.countHeight(_aboutPublicWidth);
  962. if (_channel->inviteLink().isEmpty()) {
  963. _channel->session().api().requestFullPeer(_channel);
  964. }
  965. setMouseTracking(true);
  966. _checkRequestId = _api.request(MTPchannels_CheckUsername(
  967. _channel->inputChannel,
  968. MTP_string("preston")
  969. )).fail([=](const MTP::Error &error) {
  970. _checkRequestId = 0;
  971. firstCheckFail(parseError(error.type()));
  972. }).send();
  973. addButton(tr::lng_settings_save(), [=] { save(); });
  974. const auto cancel = [=] {
  975. if (_mustBePublic) {
  976. MustBePublicDestroy(_channel);
  977. }
  978. closeBox();
  979. };
  980. addButton(
  981. _mustBePublic ? tr::lng_cancel() : tr::lng_create_group_skip(),
  982. cancel);
  983. connect(_link, &Ui::MaskedInputField::changed, [=] { handleChange(); });
  984. _link->setVisible(_privacyGroup->current() == Privacy::Public);
  985. _privacyGroup->setChangedCallback([=](Privacy value) {
  986. privacyChanged(value);
  987. });
  988. _channel->session().changes().peerUpdates(
  989. _channel,
  990. Data::PeerUpdate::Flag::InviteLinks
  991. ) | rpl::start_with_next([=] {
  992. rtlupdate(_invitationLink);
  993. }, lifetime());
  994. boxClosing() | rpl::start_with_next([=] {
  995. if (!_mustBePublic) {
  996. AddParticipantsBoxController::Start(_navigation, _channel);
  997. }
  998. }, lifetime());
  999. updateMaxHeight();
  1000. }
  1001. void SetupChannelBox::setInnerFocus() {
  1002. if (!_link->isHidden()) {
  1003. _link->setFocusFast();
  1004. } else {
  1005. BoxContent::setInnerFocus();
  1006. }
  1007. }
  1008. void SetupChannelBox::updateMaxHeight() {
  1009. auto newHeight = st::boxPadding.top()
  1010. + st::newGroupPadding.top()
  1011. + (_public
  1012. ? (_public->heightNoMargins()
  1013. + _aboutPublicHeight
  1014. + st::newGroupSkip)
  1015. : 0)
  1016. + (_private
  1017. ? (_private->heightNoMargins()
  1018. + _aboutPrivate.countHeight(_aboutPublicWidth)
  1019. + st::newGroupSkip)
  1020. : 0)
  1021. + st::newGroupPadding.bottom();
  1022. if (!_channel->isMegagroup()
  1023. || _privacyGroup->current() == Privacy::Public) {
  1024. newHeight += st::newGroupLinkPadding.top()
  1025. + _link->height()
  1026. + st::newGroupLinkPadding.bottom();
  1027. }
  1028. setDimensions(st::boxWideWidth, newHeight);
  1029. }
  1030. void SetupChannelBox::keyPressEvent(QKeyEvent *e) {
  1031. if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
  1032. if (_link->hasFocus()) {
  1033. if (_link->text().trimmed().isEmpty()) {
  1034. _link->setFocus();
  1035. _link->showError();
  1036. } else {
  1037. save();
  1038. }
  1039. }
  1040. } else {
  1041. BoxContent::keyPressEvent(e);
  1042. }
  1043. }
  1044. void SetupChannelBox::paintEvent(QPaintEvent *e) {
  1045. Painter p(this);
  1046. p.fillRect(e->rect(), st::boxBg);
  1047. p.setPen(st::newGroupAboutFg);
  1048. if (_public) {
  1049. const auto aboutPublic = QRect(
  1050. st::boxPadding.left()
  1051. + st::newGroupPadding.left()
  1052. + st::defaultRadio.diameter
  1053. + st::defaultBoxCheckbox.textPosition.x(),
  1054. _public->bottomNoMargins(),
  1055. _aboutPublicWidth,
  1056. _aboutPublicHeight);
  1057. _aboutPublic.drawLeft(
  1058. p,
  1059. aboutPublic.x(),
  1060. aboutPublic.y(),
  1061. aboutPublic.width(),
  1062. width());
  1063. }
  1064. if (_private) {
  1065. const auto aboutPrivate = QRect(
  1066. st::boxPadding.left()
  1067. + st::newGroupPadding.left()
  1068. + st::defaultRadio.diameter
  1069. + st::defaultBoxCheckbox.textPosition.x(),
  1070. _private->bottomNoMargins(),
  1071. _aboutPublicWidth,
  1072. _aboutPublicHeight);
  1073. _aboutPrivate.drawLeft(
  1074. p,
  1075. aboutPrivate.x(),
  1076. aboutPrivate.y(),
  1077. aboutPrivate.width(),
  1078. width());
  1079. }
  1080. if (!_channel->isMegagroup() || !_link->isHidden()) {
  1081. p.setPen(st::boxTextFg);
  1082. p.setFont(st::newGroupLinkFont);
  1083. p.drawTextLeft(
  1084. st::boxPadding.left()
  1085. + st::newGroupPadding.left()
  1086. + st::defaultInputField.textMargins.left(),
  1087. _link->y() - st::newGroupLinkPadding.top() + st::newGroupLinkTop,
  1088. width(),
  1089. (_link->isHidden()
  1090. ? tr::lng_create_group_invite_link
  1091. : tr::lng_create_group_link)(tr::now));
  1092. }
  1093. if (_link->isHidden()) {
  1094. if (!_channel->isMegagroup()) {
  1095. QTextOption option(style::al_left);
  1096. option.setWrapMode(QTextOption::WrapAnywhere);
  1097. p.setFont(_linkOver
  1098. ? st::boxTextFont->underline()
  1099. : st::boxTextFont);
  1100. p.setPen(st::defaultLinkButton.color);
  1101. const auto inviteLinkText = _channel->inviteLink().isEmpty()
  1102. ? tr::lng_group_invite_create(tr::now)
  1103. : _channel->inviteLink();
  1104. p.drawText(_invitationLink, inviteLinkText, option);
  1105. }
  1106. } else {
  1107. const auto top = _link->y()
  1108. - st::newGroupLinkPadding.top()
  1109. + st::newGroupLinkTop
  1110. + st::newGroupLinkFont->ascent
  1111. - st::boxTextFont->ascent;
  1112. if (!_errorText.isEmpty()) {
  1113. p.setPen(st::boxTextFgError);
  1114. p.setFont(st::boxTextFont);
  1115. p.drawTextRight(st::boxPadding.right(), top, width(), _errorText);
  1116. } else if (!_goodText.isEmpty()) {
  1117. p.setPen(st::boxTextFgGood);
  1118. p.setFont(st::boxTextFont);
  1119. p.drawTextRight(st::boxPadding.right(), top, width(), _goodText);
  1120. }
  1121. }
  1122. }
  1123. void SetupChannelBox::resizeEvent(QResizeEvent *e) {
  1124. BoxContent::resizeEvent(e);
  1125. const auto left = st::boxPadding.left() + st::newGroupPadding.left();
  1126. if (_public && _private) {
  1127. _public->moveToLeft(
  1128. left,
  1129. st::boxPadding.top() + st::newGroupPadding.top());
  1130. _private->moveToLeft(
  1131. left,
  1132. _public->bottomNoMargins() + _aboutPublicHeight + st::newGroupSkip);
  1133. }
  1134. _link->resize(
  1135. width()
  1136. - st::boxPadding.left()
  1137. - st::newGroupLinkPadding.left()
  1138. - st::boxPadding.right(),
  1139. _link->height());
  1140. _link->moveToLeft(
  1141. st::boxPadding.left() + st::newGroupLinkPadding.left(),
  1142. (st::boxPadding.top()
  1143. + st::newGroupPadding.top()
  1144. + (_public
  1145. ? (_public->heightNoMargins()
  1146. + _aboutPublicHeight
  1147. + st::newGroupSkip)
  1148. : 0)
  1149. + (_private
  1150. ? (_private->heightNoMargins()
  1151. + _aboutPrivate.countHeight(_aboutPublicWidth)
  1152. + st::newGroupSkip)
  1153. : 0)
  1154. + st::newGroupPadding.bottom()
  1155. + st::newGroupLinkPadding.top()));
  1156. _invitationLink = QRect(
  1157. _link->x(),
  1158. _link->y() + (_link->height() / 2) - st::boxTextFont->height,
  1159. _link->width(),
  1160. 2 * st::boxTextFont->height);
  1161. }
  1162. void SetupChannelBox::mouseMoveEvent(QMouseEvent *e) {
  1163. updateSelected(e->globalPos());
  1164. }
  1165. void SetupChannelBox::mousePressEvent(QMouseEvent *e) {
  1166. if (!_linkOver) {
  1167. return;
  1168. } else if (!_channel->inviteLink().isEmpty()) {
  1169. QGuiApplication::clipboard()->setText(_channel->inviteLink());
  1170. showToast(tr::lng_create_channel_link_copied(tr::now));
  1171. } else if (_channel->isFullLoaded() && !_creatingInviteLink) {
  1172. _creatingInviteLink = true;
  1173. _channel->session().api().inviteLinks().create({ _channel });
  1174. }
  1175. }
  1176. void SetupChannelBox::leaveEventHook(QEvent *e) {
  1177. updateSelected(QCursor::pos());
  1178. }
  1179. void SetupChannelBox::updateSelected(const QPoint &cursorGlobalPosition) {
  1180. QPoint p(mapFromGlobal(cursorGlobalPosition));
  1181. bool linkOver = _invitationLink.contains(p);
  1182. if (linkOver != _linkOver) {
  1183. _linkOver = linkOver;
  1184. update();
  1185. setCursor(_linkOver ? style::cur_pointer : style::cur_default);
  1186. }
  1187. }
  1188. void SetupChannelBox::save() {
  1189. const auto saveUsername = [&](const QString &link) {
  1190. _sentUsername = link;
  1191. _saveRequestId = _api.request(MTPchannels_UpdateUsername(
  1192. _channel->inputChannel,
  1193. MTP_string(_sentUsername)
  1194. )).done([=] {
  1195. const auto done = _done;
  1196. const auto channel = _channel;
  1197. _channel->setName(
  1198. TextUtilities::SingleLine(_channel->name()),
  1199. _sentUsername);
  1200. closeBox(); // Deletes `this`.
  1201. if (done) {
  1202. done(channel);
  1203. }
  1204. }).fail([=](const MTP::Error &error) {
  1205. _saveRequestId = 0;
  1206. updateFail(parseError(error.type()));
  1207. }).send();
  1208. };
  1209. if (_saveRequestId) {
  1210. return;
  1211. } else if (_privacyGroup->current() == Privacy::Private) {
  1212. closeBox();
  1213. } else {
  1214. const auto link = _link->text().trimmed();
  1215. if (link.isEmpty()) {
  1216. _link->setFocus();
  1217. _link->showError();
  1218. return;
  1219. }
  1220. saveUsername(link);
  1221. }
  1222. }
  1223. void SetupChannelBox::handleChange() {
  1224. const auto name = _link->text().trimmed();
  1225. if (name.isEmpty()) {
  1226. if (!_errorText.isEmpty() || !_goodText.isEmpty()) {
  1227. _errorText = _goodText = QString();
  1228. update();
  1229. }
  1230. _checkTimer.cancel();
  1231. } else {
  1232. const auto len = int(name.size());
  1233. for (auto i = 0; i < len; ++i) {
  1234. const auto ch = name.at(i);
  1235. if ((ch < 'A' || ch > 'Z')
  1236. && (ch < 'a' || ch > 'z')
  1237. && (ch < '0' || ch > '9')
  1238. && ch != '_') {
  1239. const auto badSymbols
  1240. = tr::lng_create_channel_link_bad_symbols(tr::now);
  1241. if (_errorText != badSymbols) {
  1242. _errorText = badSymbols;
  1243. update();
  1244. }
  1245. _checkTimer.cancel();
  1246. return;
  1247. }
  1248. }
  1249. if (name.size() < Ui::EditPeer::kMinUsernameLength) {
  1250. const auto tooShort
  1251. = tr::lng_create_channel_link_too_short(tr::now);
  1252. if (_errorText != tooShort) {
  1253. _errorText = tooShort;
  1254. update();
  1255. }
  1256. _checkTimer.cancel();
  1257. } else {
  1258. if (!_errorText.isEmpty() || !_goodText.isEmpty()) {
  1259. _errorText = _goodText = QString();
  1260. update();
  1261. }
  1262. _checkTimer.callOnce(Ui::EditPeer::kUsernameCheckTimeout);
  1263. }
  1264. }
  1265. }
  1266. void SetupChannelBox::check() {
  1267. if (_checkRequestId) {
  1268. _api.request(_checkRequestId).cancel();
  1269. }
  1270. const auto link = _link->text().trimmed();
  1271. if (link.size() >= Ui::EditPeer::kMinUsernameLength) {
  1272. _checkUsername = link;
  1273. _checkRequestId = _api.request(MTPchannels_CheckUsername(
  1274. _channel->inputChannel,
  1275. MTP_string(link)
  1276. )).done([=](const MTPBool &result) {
  1277. _checkRequestId = 0;
  1278. _errorText = (mtpIsTrue(result)
  1279. || _checkUsername == _channel->username())
  1280. ? QString()
  1281. : tr::lng_create_channel_link_occupied(tr::now);
  1282. _goodText = _errorText.isEmpty()
  1283. ? tr::lng_create_channel_link_available(tr::now)
  1284. : QString();
  1285. update();
  1286. }).fail([=](const MTP::Error &error) {
  1287. _checkRequestId = 0;
  1288. checkFail(parseError(error.type()));
  1289. }).send();
  1290. }
  1291. }
  1292. void SetupChannelBox::privacyChanged(Privacy value) {
  1293. if (value == Privacy::Public) {
  1294. if (_tooMuchUsernames) {
  1295. _privacyGroup->setValue(Privacy::Private);
  1296. const auto callback = crl::guard(this, [=] {
  1297. _tooMuchUsernames = false;
  1298. _privacyGroup->setValue(Privacy::Public);
  1299. check();
  1300. });
  1301. Ui::show(
  1302. Box(PublicLinksLimitBox, _navigation, callback),
  1303. Ui::LayerOption::KeepOther);
  1304. return;
  1305. }
  1306. _link->show();
  1307. _link->setDisplayFocused(true);
  1308. _link->setFocus();
  1309. } else {
  1310. _link->hide();
  1311. setFocus();
  1312. }
  1313. if (_channel->isMegagroup()) {
  1314. updateMaxHeight();
  1315. }
  1316. update();
  1317. }
  1318. SetupChannelBox::UsernameResult SetupChannelBox::parseError(
  1319. const QString &error) {
  1320. if (error == u"USERNAME_NOT_MODIFIED"_q) {
  1321. return UsernameResult::Ok;
  1322. } else if (error == u"USERNAME_INVALID"_q) {
  1323. return UsernameResult::Invalid;
  1324. } else if (error == u"USERNAME_OCCUPIED"_q) {
  1325. return UsernameResult::Occupied;
  1326. } else if (error == u"USERNAME_PURCHASE_AVAILABLE"_q) {
  1327. return UsernameResult::Occupied;
  1328. } else if (error == u"USERNAMES_UNAVAILABLE"_q) {
  1329. return UsernameResult::Occupied;
  1330. } else if (error == u"CHANNEL_PUBLIC_GROUP_NA"_q) {
  1331. return UsernameResult::NA;
  1332. } else if (error == u"CHANNELS_ADMIN_PUBLIC_TOO_MUCH"_q) {
  1333. return UsernameResult::ChatsTooMuch;
  1334. } else {
  1335. return UsernameResult::Unknown;
  1336. }
  1337. }
  1338. void SetupChannelBox::updateFail(UsernameResult result) {
  1339. if ((result == UsernameResult::Ok)
  1340. || (_sentUsername == _channel->username())) {
  1341. _channel->setName(
  1342. TextUtilities::SingleLine(_channel->name()),
  1343. TextUtilities::SingleLine(_sentUsername));
  1344. closeBox();
  1345. } else if (result == UsernameResult::Invalid) {
  1346. _link->setFocus();
  1347. _link->showError();
  1348. _errorText = tr::lng_create_channel_link_invalid(tr::now);
  1349. update();
  1350. } else if (result == UsernameResult::Occupied) {
  1351. _link->setFocus();
  1352. _link->showError();
  1353. _errorText = tr::lng_create_channel_link_occupied(tr::now);
  1354. update();
  1355. } else {
  1356. _link->setFocus();
  1357. }
  1358. }
  1359. void SetupChannelBox::checkFail(UsernameResult result) {
  1360. if (result == UsernameResult::NA) {
  1361. if (_mustBePublic) {
  1362. mustBePublicFailed();
  1363. }
  1364. getDelegate()->hideLayer();
  1365. } else if (result == UsernameResult::ChatsTooMuch) {
  1366. if (_mustBePublic) {
  1367. showRevokePublicLinkBoxForEdit();
  1368. } else {
  1369. _tooMuchUsernames = true;
  1370. _privacyGroup->setValue(Privacy::Private);
  1371. }
  1372. } else if (result == UsernameResult::Invalid) {
  1373. _errorText = tr::lng_create_channel_link_invalid(tr::now);
  1374. update();
  1375. } else if ((result == UsernameResult::Occupied)
  1376. && _checkUsername != _channel->username()) {
  1377. _errorText = tr::lng_create_channel_link_occupied(tr::now);
  1378. update();
  1379. } else {
  1380. _goodText = QString();
  1381. _link->setFocus();
  1382. }
  1383. }
  1384. void SetupChannelBox::showRevokePublicLinkBoxForEdit() {
  1385. const auto channel = _channel;
  1386. const auto mustBePublic = _mustBePublic;
  1387. const auto done = _done;
  1388. const auto navigation = _navigation;
  1389. const auto revoked = std::make_shared<bool>(false);
  1390. const auto callback = [=] {
  1391. *revoked = true;
  1392. navigation->parentController()->show(
  1393. Box<SetupChannelBox>(navigation, channel, mustBePublic, done));
  1394. };
  1395. const auto revoker = navigation->parentController()->show(
  1396. Box(PublicLinksLimitBox, navigation, callback));
  1397. const auto session = &navigation->session();
  1398. revoker->boxClosing(
  1399. ) | rpl::start_with_next(crl::guard(session, [=] {
  1400. base::call_delayed(200, session, [=] {
  1401. if (*revoked) {
  1402. return;
  1403. }
  1404. MustBePublicDestroy(channel);
  1405. });
  1406. }), revoker->lifetime());
  1407. closeBox();
  1408. }
  1409. void SetupChannelBox::mustBePublicFailed() {
  1410. MustBePublicFailed(_navigation, _channel);
  1411. }
  1412. void SetupChannelBox::firstCheckFail(UsernameResult result) {
  1413. if (result == UsernameResult::NA) {
  1414. if (_mustBePublic) {
  1415. mustBePublicFailed();
  1416. }
  1417. getDelegate()->hideLayer();
  1418. } else if (result == UsernameResult::ChatsTooMuch) {
  1419. if (_mustBePublic) {
  1420. showRevokePublicLinkBoxForEdit();
  1421. } else {
  1422. _tooMuchUsernames = true;
  1423. _privacyGroup->setValue(Privacy::Private);
  1424. }
  1425. } else {
  1426. _goodText = QString();
  1427. _link->setFocus();
  1428. }
  1429. }
  1430. EditNameBox::EditNameBox(QWidget*, not_null<UserData*> user)
  1431. : _user(user)
  1432. , _api(&_user->session().mtp())
  1433. , _first(
  1434. this,
  1435. st::defaultInputField,
  1436. tr::lng_signup_firstname(),
  1437. _user->firstName)
  1438. , _last(
  1439. this,
  1440. st::defaultInputField,
  1441. tr::lng_signup_lastname(),
  1442. _user->lastName)
  1443. , _invertOrder(langFirstNameGoesSecond()) {
  1444. }
  1445. void EditNameBox::prepare() {
  1446. auto newHeight = st::contactPadding.top() + _first->height();
  1447. setTitle(tr::lng_edit_self_title());
  1448. newHeight += st::contactSkip + _last->height();
  1449. newHeight += st::boxPadding.bottom() + st::contactPadding.bottom();
  1450. setDimensions(st::boxWidth, newHeight);
  1451. addButton(tr::lng_settings_save(), [=] { save(); });
  1452. addButton(tr::lng_cancel(), [=] { closeBox(); });
  1453. if (_invertOrder) {
  1454. setTabOrder(_last, _first);
  1455. }
  1456. _first->setMaxLength(Ui::EditPeer::kMaxUserFirstLastName);
  1457. _last->setMaxLength(Ui::EditPeer::kMaxUserFirstLastName);
  1458. _first->submits(
  1459. ) | rpl::start_with_next([=] { submit(); }, _first->lifetime());
  1460. _last->submits(
  1461. ) | rpl::start_with_next([=] { submit(); }, _last->lifetime());
  1462. _first->customTab(true);
  1463. _last->customTab(true);
  1464. _first->tabbed(
  1465. ) | rpl::start_with_next([=] {
  1466. _last->setFocus();
  1467. }, _first->lifetime());
  1468. _last->tabbed(
  1469. ) | rpl::start_with_next([=] {
  1470. _first->setFocus();
  1471. }, _last->lifetime());
  1472. }
  1473. void EditNameBox::setInnerFocus() {
  1474. (_invertOrder ? _last : _first)->setFocusFast();
  1475. }
  1476. void EditNameBox::submit() {
  1477. if (_first->hasFocus()) {
  1478. _last->setFocus();
  1479. } else if (_last->hasFocus()) {
  1480. if (_first->getLastText().trimmed().isEmpty()) {
  1481. _first->setFocus();
  1482. _first->showError();
  1483. } else if (_last->getLastText().trimmed().isEmpty()) {
  1484. _last->setFocus();
  1485. _last->showError();
  1486. } else {
  1487. save();
  1488. }
  1489. }
  1490. }
  1491. void EditNameBox::resizeEvent(QResizeEvent *e) {
  1492. BoxContent::resizeEvent(e);
  1493. _first->resize(
  1494. width()
  1495. - st::boxPadding.left()
  1496. - st::newGroupInfoPadding.left()
  1497. - st::boxPadding.right(),
  1498. _first->height());
  1499. _last->resize(_first->size());
  1500. const auto left = st::boxPadding.left() + st::newGroupInfoPadding.left();
  1501. const auto skip = st::contactSkip;
  1502. if (_invertOrder) {
  1503. _last->moveToLeft(left, st::contactPadding.top());
  1504. _first->moveToLeft(left, _last->y() + _last->height() + skip);
  1505. } else {
  1506. _first->moveToLeft(left, st::contactPadding.top());
  1507. _last->moveToLeft(left, _first->y() + _first->height() + skip);
  1508. }
  1509. }
  1510. void EditNameBox::save() {
  1511. if (_requestId) {
  1512. return;
  1513. }
  1514. auto first = TextUtilities::PrepareForSending(_first->getLastText());
  1515. auto last = TextUtilities::PrepareForSending(_last->getLastText());
  1516. if (first.isEmpty() && last.isEmpty()) {
  1517. if (_invertOrder) {
  1518. _last->setFocus();
  1519. _last->showError();
  1520. } else {
  1521. _first->setFocus();
  1522. _first->showError();
  1523. }
  1524. return;
  1525. }
  1526. if (first.isEmpty()) {
  1527. first = last;
  1528. last = QString();
  1529. }
  1530. _sentName = first;
  1531. auto flags = MTPaccount_UpdateProfile::Flag::f_first_name
  1532. | MTPaccount_UpdateProfile::Flag::f_last_name;
  1533. _requestId = _api.request(MTPaccount_UpdateProfile(
  1534. MTP_flags(flags),
  1535. MTP_string(first),
  1536. MTP_string(last),
  1537. MTPstring()
  1538. )).done([=](const MTPUser &user) {
  1539. _user->owner().processUser(user);
  1540. closeBox();
  1541. }).fail([=](const MTP::Error &error) {
  1542. _requestId = 0;
  1543. saveSelfFail(error.type());
  1544. }).send();
  1545. }
  1546. void EditNameBox::saveSelfFail(const QString &error) {
  1547. if (error == "NAME_NOT_MODIFIED") {
  1548. _user->setName(
  1549. TextUtilities::SingleLine(_first->getLastText().trimmed()),
  1550. TextUtilities::SingleLine(_last->getLastText().trimmed()),
  1551. QString(),
  1552. TextUtilities::SingleLine(_user->username()));
  1553. closeBox();
  1554. } else if (error == "FIRSTNAME_INVALID") {
  1555. _first->setFocus();
  1556. _first->showError();
  1557. } else if (error == "LASTNAME_INVALID") {
  1558. _last->setFocus();
  1559. _last->showError();
  1560. } else {
  1561. _first->setFocus();
  1562. }
  1563. }