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 "passport/passport_panel_edit_contact.h"
9 
10 #include "passport/passport_panel_controller.h"
11 #include "passport/ui/passport_details_row.h"
12 #include "ui/widgets/input_fields.h"
13 #include "ui/widgets/labels.h"
14 #include "ui/widgets/buttons.h"
15 #include "ui/widgets/shadow.h"
16 #include "ui/widgets/box_content_divider.h"
17 #include "ui/widgets/sent_code_field.h"
18 #include "ui/wrap/vertical_layout.h"
19 #include "ui/wrap/slide_wrap.h"
20 #include "ui/wrap/fade_wrap.h"
21 #include "ui/text/format_values.h" // Ui::FormatPhone
22 #include "ui/text/text_utilities.h" // Ui::Text::ToUpper
23 #include "ui/special_fields.h"
24 #include "boxes/abstract_box.h"
25 #include "data/data_user.h"
26 #include "countries/countries_instance.h" // Countries::ExtractPhoneCode.
27 #include "main/main_session.h"
28 #include "lang/lang_keys.h"
29 #include "styles/style_passport.h"
30 #include "styles/style_layers.h"
31 
32 namespace Passport {
33 namespace {
34 
35 class VerifyBox : public Ui::BoxContent {
36 public:
37 	VerifyBox(
38 		QWidget*,
39 		rpl::producer<QString> title,
40 		const QString &text,
41 		int codeLength,
42 		Fn<void(QString code)> submit,
43 		Fn<void()> resend,
44 		rpl::producer<QString> call,
45 		rpl::producer<QString> error,
46 		rpl::producer<QString> resent);
47 
48 	void setInnerFocus() override;
49 
50 protected:
51 	void prepare() override;
52 
53 private:
54 	void setupControls(
55 		const QString &text,
56 		int codeLength,
57 		Fn<void(QString code)> submit,
58 		Fn<void()> resend,
59 		rpl::producer<QString> call,
60 		rpl::producer<QString> error,
61 		rpl::producer<QString> resent);
62 
63 	rpl::producer<QString> _title;
64 	Fn<void()> _submit;
65 	QPointer<Ui::SentCodeField> _code;
66 	QPointer<Ui::VerticalLayout> _content;
67 
68 };
69 
VerifyBox(QWidget *,rpl::producer<QString> title,const QString & text,int codeLength,Fn<void (QString code)> submit,Fn<void ()> resend,rpl::producer<QString> call,rpl::producer<QString> error,rpl::producer<QString> resent)70 VerifyBox::VerifyBox(
71 	QWidget*,
72 	rpl::producer<QString> title,
73 	const QString &text,
74 	int codeLength,
75 	Fn<void(QString code)> submit,
76 	Fn<void()> resend,
77 	rpl::producer<QString> call,
78 	rpl::producer<QString> error,
79 	rpl::producer<QString> resent)
80 : _title(std::move(title)) {
81 	setupControls(
82 		text,
83 		codeLength,
84 		submit,
85 		resend,
86 		std::move(call),
87 		std::move(error),
88 		std::move(resent));
89 }
90 
setupControls(const QString & text,int codeLength,Fn<void (QString code)> submit,Fn<void ()> resend,rpl::producer<QString> call,rpl::producer<QString> error,rpl::producer<QString> resent)91 void VerifyBox::setupControls(
92 		const QString &text,
93 		int codeLength,
94 		Fn<void(QString code)> submit,
95 		Fn<void()> resend,
96 		rpl::producer<QString> call,
97 		rpl::producer<QString> error,
98 		rpl::producer<QString> resent) {
99 	_content = Ui::CreateChild<Ui::VerticalLayout>(this);
100 
101 	const auto small = style::margins(
102 		st::boxPadding.left(),
103 		0,
104 		st::boxPadding.right(),
105 		st::boxPadding.bottom());
106 	_content->add(
107 		object_ptr<Ui::FlatLabel>(
108 			_content,
109 			text,
110 			st::boxLabel),
111 		small);
112 	_code = _content->add(
113 		object_ptr<Ui::SentCodeField>(
114 			_content,
115 			st::defaultInputField,
116 			tr::lng_change_phone_code_title()),
117 		small);
118 
119 	const auto problem = _content->add(
120 		object_ptr<Ui::FadeWrap<Ui::FlatLabel>>(
121 			_content,
122 			object_ptr<Ui::FlatLabel>(
123 				_content,
124 				QString(),
125 				st::passportVerifyErrorLabel)),
126 		small);
127 	_content->add(
128 		object_ptr<Ui::FlatLabel>(
129 			_content,
130 			std::move(call),
131 			st::boxDividerLabel),
132 		small);
133 	if (resend) {
134 		auto link = TextWithEntities{ tr::lng_cloud_password_resend(tr::now) };
135 		link.entities.push_back({
136 			EntityType::CustomUrl,
137 			0,
138 			int(link.text.size()),
139 			QString("internal:resend") });
140 		const auto label = _content->add(
141 			object_ptr<Ui::FlatLabel>(
142 				_content,
143 				rpl::single(
144 					link
145 				) | rpl::then(rpl::duplicate(
146 					resent
147 				) | rpl::map([](const QString &value) {
148 					return TextWithEntities{ value };
149 				})),
150 				st::boxDividerLabel),
151 			small);
152 		std::move(
153 			resent
154 		) | rpl::start_with_next([=] {
155 			_content->resizeToWidth(st::boxWidth);
156 		}, _content->lifetime());
157 		label->setClickHandlerFilter([=](auto&&...) {
158 			resend();
159 			return false;
160 		});
161 	}
162 	std::move(
163 		error
164 	) | rpl::start_with_next([=](const QString &error) {
165 		if (error.isEmpty()) {
166 			problem->hide(anim::type::normal);
167 		} else {
168 			problem->entity()->setText(error);
169 			_content->resizeToWidth(st::boxWidth);
170 			problem->show(anim::type::normal);
171 			_code->showError();
172 		}
173 	}, lifetime());
174 
175 	_submit = [=] {
176 		submit(_code->getDigitsOnly());
177 	};
178 	if (codeLength > 0) {
179 		_code->setAutoSubmit(codeLength, _submit);
180 	} else {
181 		connect(_code, &Ui::SentCodeField::submitted, _submit);
182 	}
183 	connect(_code, &Ui::SentCodeField::changed, [=] {
184 		problem->hide(anim::type::normal);
185 	});
186 }
187 
setInnerFocus()188 void VerifyBox::setInnerFocus() {
189 	_code->setFocusFast();
190 }
191 
prepare()192 void VerifyBox::prepare() {
193 	setTitle(std::move(_title));
194 
195 	addButton(tr::lng_change_phone_new_submit(), _submit);
196 	addButton(tr::lng_cancel(), [=] { closeBox(); });
197 
198 	_content->resizeToWidth(st::boxWidth);
199 	_content->heightValue(
200 	) | rpl::start_with_next([=](int height) {
201 		setDimensions(st::boxWidth, height);
202 	}, _content->lifetime());
203 }
204 
205 } // namespace
206 
EditContactScheme(ValueType type)207 EditContactScheme::EditContactScheme(ValueType type) : type(type) {
208 }
209 
PanelEditContact(QWidget *,not_null<PanelController * > controller,Scheme scheme,const QString & data,const QString & existing)210 PanelEditContact::PanelEditContact(
211 	QWidget*,
212 	not_null<PanelController*> controller,
213 	Scheme scheme,
214 	const QString &data,
215 	const QString &existing)
216 : _controller(controller)
217 , _scheme(std::move(scheme))
218 , _content(this)
219 , _bottomShadow(this)
220 , _done(
221 		this,
222 		tr::lng_passport_save_value(),
223 		st::passportPanelSaveValue) {
224 	setupControls(data, existing);
225 }
226 
setupControls(const QString & data,const QString & existing)227 void PanelEditContact::setupControls(
228 		const QString &data,
229 		const QString &existing) {
230 	widthValue(
231 	) | rpl::start_with_next([=](int width) {
232 		_content->resizeToWidth(width);
233 	}, _content->lifetime());
234 
235 	_content->add(object_ptr<Ui::BoxContentDivider>(
236 		_content,
237 		st::passportFormDividerHeight));
238 	if (!existing.isEmpty()) {
239 		_content->add(
240 			object_ptr<Ui::SettingsButton>(
241 				_content,
242 				tr::lng_passport_use_existing(
243 					lt_existing,
244 					rpl::single(_scheme.format
245 						? _scheme.format(existing)
246 						: existing)),
247 				st::passportUploadButton),
248 			st::passportUploadButtonPadding
249 		)->addClickHandler([=] {
250 			save(existing);
251 		});
252 		_content->add(
253 			object_ptr<Ui::DividerLabel>(
254 				_content,
255 				object_ptr<Ui::FlatLabel>(
256 					_content,
257 					_scheme.aboutExisting,
258 					st::boxDividerLabel),
259 				st::passportFormLabelPadding));
260 		_content->add(
261 			object_ptr<Ui::FlatLabel>(
262 				_content,
263 				_scheme.newHeader,
264 				st::passportFormHeader),
265 			st::passportDetailsHeaderPadding);
266 	}
267 	const auto &fieldStyle = existing.isEmpty()
268 		? st::passportContactField
269 		: st::passportDetailsField;
270 	const auto fieldPadding = existing.isEmpty()
271 		? st::passportContactFieldPadding
272 		: st::passportContactNewFieldPadding;
273 	auto fieldPlaceholder = existing.isEmpty()
274 		? rpl::duplicate(_scheme.newPlaceholder)
275 		: nullptr;
276 	auto wrap = object_ptr<Ui::RpWidget>(_content);
277 	if (_scheme.type == Scheme::ValueType::Phone) {
278 		_field = Ui::CreateChild<Ui::PhoneInput>(
279 			wrap.data(),
280 			fieldStyle,
281 			std::move(fieldPlaceholder),
282 			Countries::ExtractPhoneCode(
283 				_controller->bot()->session().user()->phone()),
284 			data);
285 	} else {
286 		_field = Ui::CreateChild<Ui::MaskedInputField>(
287 			wrap.data(),
288 			fieldStyle,
289 			std::move(fieldPlaceholder),
290 			data);
291 	}
292 
293 	_field->move(0, 0);
294 	_field->heightValue(
295 	) | rpl::start_with_next([=, pointer = wrap.data()](int height) {
296 		pointer->resize(pointer->width(), height);
297 	}, _field->lifetime());
298 	wrap->widthValue(
299 	) | rpl::start_with_next([=](int width) {
300 		_field->resize(width, _field->height());
301 	}, _field->lifetime());
302 
303 	_content->add(std::move(wrap), fieldPadding);
304 	const auto errorWrap = _content->add(
305 		object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
306 			_content,
307 			object_ptr<Ui::FlatLabel>(
308 				_content,
309 				QString(),
310 				st::passportVerifyErrorLabel),
311 			st::passportContactErrorPadding),
312 		st::passportContactErrorMargin);
313 	errorWrap->hide(anim::type::instant);
314 
315 	_content->add(
316 		object_ptr<Ui::DividerLabel>(
317 			_content,
318 			object_ptr<Ui::FlatLabel>(
319 				_content,
320 				_scheme.aboutNew,
321 				st::boxDividerLabel),
322 			st::passportFormLabelPadding));
323 
324 	if (auto text = _controller->deleteValueLabel()) {
325 		_content->add(
326 			object_ptr<Ui::SettingsButton>(
327 				_content,
328 				std::move(*text) | Ui::Text::ToUpper(),
329 				st::passportDeleteButton),
330 			st::passportUploadButtonPadding
331 		)->addClickHandler([=] {
332 			_controller->deleteValue();
333 		});
334 	}
335 
336 	_controller->saveErrors(
337 	) | rpl::start_with_next([=](const ScopeError &error) {
338 		if (error.key == QString("value")) {
339 			_field->showError();
340 			errorWrap->entity()->setText(error.text);
341 			_content->resizeToWidth(width());
342 			errorWrap->show(anim::type::normal);
343 		}
344 	}, lifetime());
345 
346 	const auto submit = [=] {
347 		crl::on_main(this, [=] {
348 			save();
349 		});
350 	};
351 	connect(_field, &Ui::MaskedInputField::submitted, submit);
352 	connect(_field, &Ui::MaskedInputField::changed, [=] {
353 		errorWrap->hide(anim::type::normal);
354 	});
355 	_done->addClickHandler(submit);
356 }
357 
focusInEvent(QFocusEvent * e)358 void PanelEditContact::focusInEvent(QFocusEvent *e) {
359 	crl::on_main(this, [=] {
360 		_field->setFocusFast();
361 	});
362 }
363 
resizeEvent(QResizeEvent * e)364 void PanelEditContact::resizeEvent(QResizeEvent *e) {
365 	updateControlsGeometry();
366 }
367 
updateControlsGeometry()368 void PanelEditContact::updateControlsGeometry() {
369 	const auto submitTop = height() - _done->height();
370 	_bottomShadow->resizeToWidth(width());
371 	_bottomShadow->moveToLeft(0, submitTop - st::lineWidth);
372 	_done->resizeToWidth(width());
373 	_done->moveToLeft(0, submitTop);
374 }
375 
save()376 void PanelEditContact::save() {
377 	const auto result = _field->getLastText();
378 	const auto processed = _scheme.postprocess
379 		? _scheme.postprocess(result)
380 		: result;
381 	if (_scheme.validate && !_scheme.validate(processed)) {
382 		_field->showError();
383 		return;
384 	}
385 	save(processed);
386 }
387 
save(const QString & value)388 void PanelEditContact::save(const QString &value) {
389 	auto data = ValueMap();
390 	data.fields["value"].text = value;
391 	_controller->saveScope(std::move(data), {});
392 }
393 
VerifyPhoneBox(const QString & phone,int codeLength,Fn<void (QString code)> submit,rpl::producer<QString> call,rpl::producer<QString> error)394 object_ptr<Ui::BoxContent> VerifyPhoneBox(
395 		const QString &phone,
396 		int codeLength,
397 		Fn<void(QString code)> submit,
398 		rpl::producer<QString> call,
399 		rpl::producer<QString> error) {
400 	return Box<VerifyBox>(
401 		tr::lng_passport_phone_title(),
402 		tr::lng_passport_confirm_phone(
403 			tr::now,
404 			lt_phone,
405 			Ui::FormatPhone(phone)),
406 		codeLength,
407 		submit,
408 		nullptr,
409 		std::move(call),
410 		std::move(error),
411 		nullptr);
412 }
413 
VerifyEmailBox(const QString & email,int codeLength,Fn<void (QString code)> submit,Fn<void ()> resend,rpl::producer<QString> error,rpl::producer<QString> resent)414 object_ptr<Ui::BoxContent> VerifyEmailBox(
415 		const QString &email,
416 		int codeLength,
417 		Fn<void(QString code)> submit,
418 		Fn<void()> resend,
419 		rpl::producer<QString> error,
420 		rpl::producer<QString> resent) {
421 	return Box<VerifyBox>(
422 		tr::lng_passport_email_title(),
423 		tr::lng_passport_confirm_email(tr::now, lt_email, email),
424 		codeLength,
425 		submit,
426 		resend,
427 		rpl::single(QString()),
428 		std::move(error),
429 		std::move(resent));
430 }
431 
432 } // namespace Passport
433