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