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