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