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/connection_box.h"
9
10 #include "ui/boxes/confirm_box.h"
11 #include "lang/lang_keys.h"
12 #include "storage/localstorage.h"
13 #include "base/qthelp_url.h"
14 #include "base/call_delayed.h"
15 #include "core/application.h"
16 #include "core/core_settings.h"
17 #include "main/main_account.h"
18 #include "mtproto/facade.h"
19 #include "ui/widgets/checkbox.h"
20 #include "ui/widgets/buttons.h"
21 #include "ui/widgets/input_fields.h"
22 #include "ui/widgets/labels.h"
23 #include "ui/widgets/dropdown_menu.h"
24 #include "ui/wrap/slide_wrap.h"
25 #include "ui/wrap/vertical_layout.h"
26 #include "ui/toast/toast.h"
27 #include "ui/effects/animations.h"
28 #include "ui/effects/radial_animation.h"
29 #include "ui/text/text_options.h"
30 #include "ui/basic_click_handlers.h"
31 #include "styles/style_layers.h"
32 #include "styles/style_boxes.h"
33 #include "styles/style_chat_helpers.h"
34 #include "styles/style_info.h"
35
36 #include <QtGui/QGuiApplication>
37 #include <QtGui/QClipboard>
38
39 namespace {
40
41 constexpr auto kSaveSettingsDelayedTimeout = crl::time(1000);
42
43 using ProxyData = MTP::ProxyData;
44
45 class HostInput : public Ui::MaskedInputField {
46 public:
47 HostInput(
48 QWidget *parent,
49 const style::InputField &st,
50 rpl::producer<QString> placeholder,
51 const QString &val);
52
53 protected:
54 void correctValue(
55 const QString &was,
56 int wasCursor,
57 QString &now,
58 int &nowCursor) override;
59 };
60
HostInput(QWidget * parent,const style::InputField & st,rpl::producer<QString> placeholder,const QString & val)61 HostInput::HostInput(
62 QWidget *parent,
63 const style::InputField &st,
64 rpl::producer<QString> placeholder,
65 const QString &val)
66 : MaskedInputField(parent, st, std::move(placeholder), val) {
67 }
68
correctValue(const QString & was,int wasCursor,QString & now,int & nowCursor)69 void HostInput::correctValue(
70 const QString &was,
71 int wasCursor,
72 QString &now,
73 int &nowCursor) {
74 QString newText;
75 int newCursor = nowCursor;
76 newText.reserve(now.size());
77 for (auto i = 0, l = int(now.size()); i < l; ++i) {
78 if (now[i] == ',') {
79 newText.append('.');
80 } else {
81 newText.append(now[i]);
82 }
83 }
84 setCorrectedText(now, nowCursor, newText, newCursor);
85 }
86
87 class Base64UrlInput : public Ui::MaskedInputField {
88 public:
89 Base64UrlInput(
90 QWidget *parent,
91 const style::InputField &st,
92 rpl::producer<QString> placeholder,
93 const QString &val);
94
95 protected:
96 void correctValue(
97 const QString &was,
98 int wasCursor,
99 QString &now,
100 int &nowCursor) override;
101
102 };
103
Base64UrlInput(QWidget * parent,const style::InputField & st,rpl::producer<QString> placeholder,const QString & val)104 Base64UrlInput::Base64UrlInput(
105 QWidget *parent,
106 const style::InputField &st,
107 rpl::producer<QString> placeholder,
108 const QString &val)
109 : MaskedInputField(parent, st, std::move(placeholder), val) {
110 if (!QRegularExpression("^[a-zA-Z0-9_\\-]+$").match(val).hasMatch()) {
111 setText(QString());
112 }
113 }
114
correctValue(const QString & was,int wasCursor,QString & now,int & nowCursor)115 void Base64UrlInput::correctValue(
116 const QString &was,
117 int wasCursor,
118 QString &now,
119 int &nowCursor) {
120 QString newText;
121 newText.reserve(now.size());
122 auto newPos = nowCursor;
123 for (auto i = 0, l = int(now.size()); i < l; ++i) {
124 const auto ch = now[i];
125 if ((ch >= '0' && ch <= '9')
126 || (ch >= 'a' && ch <= 'z')
127 || (ch >= 'A' && ch <= 'Z')
128 || (ch == '-')
129 || (ch == '_')) {
130 newText.append(ch);
131 } else if (i < nowCursor) {
132 --newPos;
133 }
134 }
135 setCorrectedText(now, nowCursor, newText, newPos);
136 }
137
138 class ProxyRow : public Ui::RippleButton {
139 public:
140 using View = ProxiesBoxController::ItemView;
141 using State = ProxiesBoxController::ItemState;
142
143 ProxyRow(QWidget *parent, View &&view);
144
145 void updateFields(View &&view);
146
147 rpl::producer<> deleteClicks() const;
148 rpl::producer<> restoreClicks() const;
149 rpl::producer<> editClicks() const;
150 rpl::producer<> shareClicks() const;
151
152 protected:
153 int resizeGetHeight(int newWidth) override;
154
155 void paintEvent(QPaintEvent *e) override;
156
157 private:
158 void setupControls(View &&view);
159 int countAvailableWidth() const;
160 void radialAnimationCallback();
161 void paintCheck(Painter &p);
162 void showMenu();
163
164 View _view;
165
166 Ui::Text::String _title;
167 object_ptr<Ui::IconButton> _menuToggle;
168 rpl::event_stream<> _deleteClicks;
169 rpl::event_stream<> _restoreClicks;
170 rpl::event_stream<> _editClicks;
171 rpl::event_stream<> _shareClicks;
172 base::unique_qptr<Ui::DropdownMenu> _menu;
173
174 bool _set = false;
175 Ui::Animations::Simple _toggled;
176 Ui::Animations::Simple _setAnimation;
177 std::unique_ptr<Ui::InfiniteRadialAnimation> _progress;
178 std::unique_ptr<Ui::InfiniteRadialAnimation> _checking;
179
180 int _skipLeft = 0;
181 int _skipRight = 0;
182
183 };
184
185 class ProxiesBox : public Ui::BoxContent {
186 public:
187 using View = ProxiesBoxController::ItemView;
188
189 ProxiesBox(
190 QWidget*,
191 not_null<ProxiesBoxController*> controller,
192 Core::SettingsProxy &settings);
193
194 protected:
195 void prepare() override;
196
197 private:
198 void setupContent();
199 void createNoRowsLabel();
200 void addNewProxy();
201 void applyView(View &&view);
202 void setupButtons(int id, not_null<ProxyRow*> button);
203 int rowHeight() const;
204 void refreshProxyForCalls();
205
206 not_null<ProxiesBoxController*> _controller;
207 Core::SettingsProxy &_settings;
208 QPointer<Ui::Checkbox> _tryIPv6;
209 std::shared_ptr<Ui::RadioenumGroup<ProxyData::Settings>> _proxySettings;
210 QPointer<Ui::SlideWrap<Ui::Checkbox>> _proxyForCalls;
211 QPointer<Ui::DividerLabel> _about;
212 base::unique_qptr<Ui::RpWidget> _noRows;
213 object_ptr<Ui::VerticalLayout> _initialWrap;
214 QPointer<Ui::VerticalLayout> _wrap;
215 int _currentProxySupportsCallsId = 0;
216
217 base::flat_map<int, base::unique_qptr<ProxyRow>> _rows;
218
219 };
220
221 class ProxyBox final : public Ui::BoxContent {
222 public:
223 ProxyBox(
224 QWidget*,
225 const ProxyData &data,
226 Fn<void(ProxyData)> callback,
227 Fn<void(ProxyData)> shareCallback);
228
229 private:
230 using Type = ProxyData::Type;
231
232 void prepare() override;
setInnerFocus()233 void setInnerFocus() override {
234 _host->setFocusFast();
235 }
236
237 void refreshButtons();
238 ProxyData collectData();
239 void save();
240 void share();
241 void setupControls(const ProxyData &data);
242 void setupTypes();
243 void setupSocketAddress(const ProxyData &data);
244 void setupCredentials(const ProxyData &data);
245 void setupMtprotoCredentials(const ProxyData &data);
246
247 void addLabel(
248 not_null<Ui::VerticalLayout*> parent,
249 const QString &text) const;
250
251 Fn<void(ProxyData)> _callback;
252 Fn<void(ProxyData)> _shareCallback;
253
254 object_ptr<Ui::VerticalLayout> _content;
255
256 std::shared_ptr<Ui::RadioenumGroup<Type>> _type;
257
258 QPointer<Ui::SlideWrap<>> _aboutSponsored;
259 QPointer<HostInput> _host;
260 QPointer<Ui::NumberInput> _port;
261 QPointer<Ui::InputField> _user;
262 QPointer<Ui::PasswordInput> _password;
263 QPointer<Base64UrlInput> _secret;
264
265 QPointer<Ui::SlideWrap<Ui::VerticalLayout>> _credentials;
266 QPointer<Ui::SlideWrap<Ui::VerticalLayout>> _mtprotoCredentials;
267
268 };
269
ProxyRow(QWidget * parent,View && view)270 ProxyRow::ProxyRow(QWidget *parent, View &&view)
271 : RippleButton(parent, st::proxyRowRipple)
272 , _menuToggle(this, st::topBarMenuToggle) {
273 setupControls(std::move(view));
274 }
275
deleteClicks() const276 rpl::producer<> ProxyRow::deleteClicks() const {
277 return _deleteClicks.events();
278 }
279
restoreClicks() const280 rpl::producer<> ProxyRow::restoreClicks() const {
281 return _restoreClicks.events();
282 }
283
editClicks() const284 rpl::producer<> ProxyRow::editClicks() const {
285 return _editClicks.events();
286 }
287
shareClicks() const288 rpl::producer<> ProxyRow::shareClicks() const {
289 return _shareClicks.events();
290 }
291
setupControls(View && view)292 void ProxyRow::setupControls(View &&view) {
293 updateFields(std::move(view));
294 _toggled.stop();
295 _setAnimation.stop();
296
297 _menuToggle->addClickHandler([=] { showMenu(); });
298 }
299
countAvailableWidth() const300 int ProxyRow::countAvailableWidth() const {
301 return width() - _skipLeft - _skipRight;
302 }
303
updateFields(View && view)304 void ProxyRow::updateFields(View &&view) {
305 if (_view.selected != view.selected) {
306 _toggled.start(
307 [=] { update(); },
308 view.selected ? 0. : 1.,
309 view.selected ? 1. : 0.,
310 st::defaultRadio.duration);
311 }
312 _view = std::move(view);
313 const auto endpoint = _view.host + ':' + QString::number(_view.port);
314 _title.setText(
315 st::proxyRowTitleStyle,
316 _view.type + ' ' + textcmdLink(1, endpoint),
317 Ui::ItemTextDefaultOptions());
318
319 const auto state = _view.state;
320 if (state == State::Connecting) {
321 if (!_progress) {
322 _progress = std::make_unique<Ui::InfiniteRadialAnimation>(
323 [=] { radialAnimationCallback(); },
324 st::proxyCheckingAnimation);
325 }
326 _progress->start();
327 } else if (_progress) {
328 _progress->stop();
329 }
330 if (state == State::Checking) {
331 if (!_checking) {
332 _checking = std::make_unique<Ui::InfiniteRadialAnimation>(
333 [=] { radialAnimationCallback(); },
334 st::proxyCheckingAnimation);
335 _checking->start();
336 }
337 } else {
338 _checking = nullptr;
339 }
340 const auto set = (state == State::Connecting || state == State::Online);
341 if (_set != set) {
342 _set = set;
343 _setAnimation.start(
344 [=] { update(); },
345 _set ? 0. : 1.,
346 _set ? 1. : 0.,
347 st::defaultRadio.duration);
348 }
349
350 setPointerCursor(!_view.deleted);
351
352 update();
353 }
354
radialAnimationCallback()355 void ProxyRow::radialAnimationCallback() {
356 if (!anim::Disabled()) {
357 update();
358 }
359 }
360
resizeGetHeight(int newWidth)361 int ProxyRow::resizeGetHeight(int newWidth) {
362 const auto result = st::proxyRowPadding.top()
363 + st::semiboldFont->height
364 + st::proxyRowSkip
365 + st::normalFont->height
366 + st::proxyRowPadding.bottom();
367 auto right = st::proxyRowPadding.right();
368 _menuToggle->moveToRight(
369 right,
370 (result - _menuToggle->height()) / 2,
371 newWidth);
372 right += _menuToggle->width();
373 _skipRight = right;
374 _skipLeft = st::proxyRowPadding.left()
375 + st::proxyRowIconSkip;
376 return result;
377 }
378
paintEvent(QPaintEvent * e)379 void ProxyRow::paintEvent(QPaintEvent *e) {
380 Painter p(this);
381
382 if (!_view.deleted) {
383 paintRipple(p, 0, 0);
384 }
385
386 const auto left = _skipLeft;
387 const auto availableWidth = countAvailableWidth();
388 auto top = st::proxyRowPadding.top();
389
390 if (_view.deleted) {
391 p.setOpacity(st::stickersRowDisabledOpacity);
392 }
393
394 paintCheck(p);
395
396 p.setPen(st::proxyRowTitleFg);
397 p.setFont(st::semiboldFont);
398 p.setTextPalette(st::proxyRowTitlePalette);
399 _title.drawLeftElided(p, left, top, availableWidth, width());
400 top += st::semiboldFont->height + st::proxyRowSkip;
401
402 const auto statusFg = [&] {
403 switch (_view.state) {
404 case State::Online:
405 return st::proxyRowStatusFgOnline;
406 case State::Unavailable:
407 return st::proxyRowStatusFgOffline;
408 case State::Available:
409 return st::proxyRowStatusFgAvailable;
410 default:
411 return st::proxyRowStatusFg;
412 }
413 }();
414 const auto status = [&] {
415 switch (_view.state) {
416 case State::Available:
417 return tr::lng_proxy_available(
418 tr::now,
419 lt_ping,
420 QString::number(_view.ping));
421 case State::Checking:
422 return tr::lng_proxy_checking(tr::now);
423 case State::Connecting:
424 return tr::lng_proxy_connecting(tr::now);
425 case State::Online:
426 return tr::lng_proxy_online(tr::now);
427 case State::Unavailable:
428 return tr::lng_proxy_unavailable(tr::now);
429 }
430 Unexpected("State in ProxyRow::paintEvent.");
431 }();
432 p.setPen(_view.deleted ? st::proxyRowStatusFg : statusFg);
433 p.setFont(st::normalFont);
434
435 auto statusLeft = left;
436 if (_checking) {
437 _checking->draw(
438 p,
439 {
440 st::proxyCheckingPosition.x() + statusLeft,
441 st::proxyCheckingPosition.y() + top
442 },
443 width());
444 statusLeft += st::proxyCheckingPosition.x()
445 + st::proxyCheckingAnimation.size.width()
446 + st::proxyCheckingSkip;
447 }
448 p.drawTextLeft(statusLeft, top, width(), status);
449 top += st::normalFont->height + st::proxyRowPadding.bottom();
450 }
451
paintCheck(Painter & p)452 void ProxyRow::paintCheck(Painter &p) {
453 const auto loading = _progress
454 ? _progress->computeState()
455 : Ui::RadialState{ 0., 0, FullArcLength };
456 const auto toggled = _toggled.value(_view.selected ? 1. : 0.)
457 * (1. - loading.shown);
458 const auto _st = &st::defaultRadio;
459 const auto set = _setAnimation.value(_set ? 1. : 0.);
460
461 PainterHighQualityEnabler hq(p);
462
463 const auto left = st::proxyRowPadding.left();
464 const auto top = (height() - _st->diameter - _st->thickness) / 2;
465 const auto outerWidth = width();
466
467 auto pen = anim::pen(_st->untoggledFg, _st->toggledFg, toggled * set);
468 pen.setWidth(_st->thickness);
469 pen.setCapStyle(Qt::RoundCap);
470 p.setPen(pen);
471 p.setBrush(_st->bg);
472 const auto rect = style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(_st->thickness / 2., _st->thickness / 2., _st->thickness / 2., _st->thickness / 2.)), outerWidth);
473 if (_progress && loading.shown > 0 && anim::Disabled()) {
474 anim::DrawStaticLoading(
475 p,
476 rect,
477 _st->thickness,
478 pen.color(),
479 _st->bg);
480 } else if (loading.arcLength < FullArcLength) {
481 p.drawArc(rect, loading.arcFrom, loading.arcLength);
482 } else {
483 p.drawEllipse(rect);
484 }
485
486 if (toggled > 0 && (!_progress || !anim::Disabled())) {
487 p.setPen(Qt::NoPen);
488 p.setBrush(anim::brush(_st->untoggledFg, _st->toggledFg, toggled * set));
489
490 auto skip0 = _st->diameter / 2., skip1 = _st->skip / 10., checkSkip = skip0 * (1. - toggled) + skip1 * toggled;
491 p.drawEllipse(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(checkSkip, checkSkip, checkSkip, checkSkip)), outerWidth));
492 }
493 }
494
showMenu()495 void ProxyRow::showMenu() {
496 if (_menu) {
497 return;
498 }
499 _menu = base::make_unique_q<Ui::DropdownMenu>(window());
500 const auto weak = _menu.get();
501 _menu->setHiddenCallback([=] {
502 weak->deleteLater();
503 if (_menu == weak) {
504 _menuToggle->setForceRippled(false);
505 }
506 });
507 _menu->setShowStartCallback([=] {
508 if (_menu == weak) {
509 _menuToggle->setForceRippled(true);
510 }
511 });
512 _menu->setHideStartCallback([=] {
513 if (_menu == weak) {
514 _menuToggle->setForceRippled(false);
515 }
516 });
517 _menuToggle->installEventFilter(_menu);
518 const auto addAction = [&](
519 const QString &text,
520 Fn<void()> callback) {
521 return _menu->addAction(text, std::move(callback));
522 };
523 addAction(tr::lng_proxy_menu_edit(tr::now), [=] {
524 _editClicks.fire({});
525 });
526 if (_view.supportsShare) {
527 addAction(tr::lng_proxy_edit_share(tr::now), [=] {
528 _shareClicks.fire({});
529 });
530 }
531 if (_view.deleted) {
532 addAction(tr::lng_proxy_menu_restore(tr::now), [=] {
533 _restoreClicks.fire({});
534 });
535 } else {
536 addAction(tr::lng_proxy_menu_delete(tr::now), [=] {
537 _deleteClicks.fire({});
538 });
539 }
540 const auto parentTopLeft = window()->mapToGlobal(QPoint());
541 const auto buttonTopLeft = _menuToggle->mapToGlobal(QPoint());
542 const auto parent = QRect(parentTopLeft, window()->size());
543 const auto button = QRect(buttonTopLeft, _menuToggle->size());
544 const auto bottom = button.y()
545 + st::proxyDropdownDownPosition.y()
546 + _menu->height()
547 - parent.y();
548 const auto top = button.y()
549 + st::proxyDropdownUpPosition.y()
550 - _menu->height()
551 - parent.y();
552 if (bottom > parent.height() && top >= 0) {
553 const auto left = button.x()
554 + button.width()
555 + st::proxyDropdownUpPosition.x()
556 - _menu->width()
557 - parent.x();
558 _menu->move(left, top);
559 _menu->showAnimated(Ui::PanelAnimation::Origin::BottomRight);
560 } else {
561 const auto left = button.x()
562 + button.width()
563 + st::proxyDropdownDownPosition.x()
564 - _menu->width()
565 - parent.x();
566 _menu->move(left, bottom - _menu->height());
567 _menu->showAnimated(Ui::PanelAnimation::Origin::TopRight);
568 }
569 }
570
ProxiesBox(QWidget *,not_null<ProxiesBoxController * > controller,Core::SettingsProxy & settings)571 ProxiesBox::ProxiesBox(
572 QWidget*,
573 not_null<ProxiesBoxController*> controller,
574 Core::SettingsProxy &settings)
575 : _controller(controller)
576 , _settings(settings)
577 , _initialWrap(this) {
578 _controller->views(
579 ) | rpl::start_with_next([=](View &&view) {
580 applyView(std::move(view));
581 }, lifetime());
582 }
583
prepare()584 void ProxiesBox::prepare() {
585 setTitle(tr::lng_proxy_settings());
586
587 addButton(tr::lng_proxy_add(), [=] { addNewProxy(); });
588 addButton(tr::lng_close(), [=] { closeBox(); });
589
590 setupContent();
591 }
592
setupContent()593 void ProxiesBox::setupContent() {
594 const auto inner = setInnerWidget(object_ptr<Ui::VerticalLayout>(this));
595
596 _tryIPv6 = inner->add(
597 object_ptr<Ui::Checkbox>(
598 inner,
599 tr::lng_connection_try_ipv6(tr::now),
600 _settings.tryIPv6()),
601 st::proxyTryIPv6Padding);
602 _proxySettings
603 = std::make_shared<Ui::RadioenumGroup<ProxyData::Settings>>(
604 _settings.settings());
605 inner->add(
606 object_ptr<Ui::Radioenum<ProxyData::Settings>>(
607 inner,
608 _proxySettings,
609 ProxyData::Settings::Disabled,
610 tr::lng_proxy_disable(tr::now)),
611 st::proxyUsePadding);
612 inner->add(
613 object_ptr<Ui::Radioenum<ProxyData::Settings>>(
614 inner,
615 _proxySettings,
616 ProxyData::Settings::System,
617 tr::lng_proxy_use_system_settings(tr::now)),
618 st::proxyUsePadding);
619 inner->add(
620 object_ptr<Ui::Radioenum<ProxyData::Settings>>(
621 inner,
622 _proxySettings,
623 ProxyData::Settings::Enabled,
624 tr::lng_proxy_use_custom(tr::now)),
625 st::proxyUsePadding);
626 _proxyForCalls = inner->add(
627 object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
628 inner,
629 object_ptr<Ui::Checkbox>(
630 inner,
631 tr::lng_proxy_use_for_calls(tr::now),
632 _settings.useProxyForCalls()),
633 style::margins(
634 0,
635 st::proxyUsePadding.top(),
636 0,
637 st::proxyUsePadding.bottom())),
638 style::margins(
639 st::proxyTryIPv6Padding.left(),
640 0,
641 st::proxyTryIPv6Padding.right(),
642 st::proxyTryIPv6Padding.top()));
643
644 _about = inner->add(
645 object_ptr<Ui::DividerLabel>(
646 inner,
647 object_ptr<Ui::FlatLabel>(
648 inner,
649 tr::lng_proxy_about(tr::now),
650 st::boxDividerLabel),
651 st::proxyAboutPadding),
652 style::margins(0, 0, 0, st::proxyRowPadding.top()));
653
654 _wrap = inner->add(std::move(_initialWrap));
655 inner->add(object_ptr<Ui::FixedHeightWidget>(
656 inner,
657 st::proxyRowPadding.bottom()));
658
659 _proxySettings->setChangedCallback([=](ProxyData::Settings value) {
660 if (!_controller->setProxySettings(value)) {
661 _proxySettings->setValue(_settings.settings());
662 addNewProxy();
663 }
664 refreshProxyForCalls();
665 });
666 _tryIPv6->checkedChanges(
667 ) | rpl::start_with_next([=](bool checked) {
668 _controller->setTryIPv6(checked);
669 }, _tryIPv6->lifetime());
670
671 _controller->proxySettingsValue(
672 ) | rpl::start_with_next([=](ProxyData::Settings value) {
673 _proxySettings->setValue(value);
674 }, inner->lifetime());
675
676 _proxyForCalls->entity()->checkedChanges(
677 ) | rpl::start_with_next([=](bool checked) {
678 _controller->setProxyForCalls(checked);
679 }, _proxyForCalls->lifetime());
680
681 if (_rows.empty()) {
682 createNoRowsLabel();
683 }
684 refreshProxyForCalls();
685 _proxyForCalls->finishAnimating();
686
687 inner->resizeToWidth(st::boxWideWidth);
688
689 inner->heightValue(
690 ) | rpl::map([=](int height) {
691 return std::min(
692 std::max(height, _about->y()
693 + _about->height()
694 + 3 * rowHeight()),
695 st::boxMaxListHeight);
696 }) | rpl::distinct_until_changed(
697 ) | rpl::start_with_next([=](int height) {
698 setDimensions(st::boxWideWidth, height);
699 }, inner->lifetime());
700 }
701
refreshProxyForCalls()702 void ProxiesBox::refreshProxyForCalls() {
703 if (!_proxyForCalls) {
704 return;
705 }
706 _proxyForCalls->toggle(
707 (_proxySettings->value() == ProxyData::Settings::Enabled
708 && _currentProxySupportsCallsId != 0),
709 anim::type::normal);
710 }
711
rowHeight() const712 int ProxiesBox::rowHeight() const {
713 return st::proxyRowPadding.top()
714 + st::semiboldFont->height
715 + st::proxyRowSkip
716 + st::normalFont->height
717 + st::proxyRowPadding.bottom();
718 }
719
addNewProxy()720 void ProxiesBox::addNewProxy() {
721 getDelegate()->show(_controller->addNewItemBox());
722 }
723
applyView(View && view)724 void ProxiesBox::applyView(View &&view) {
725 if (view.selected) {
726 _currentProxySupportsCallsId = view.supportsCalls ? view.id : 0;
727 } else if (view.id == _currentProxySupportsCallsId) {
728 _currentProxySupportsCallsId = 0;
729 }
730 refreshProxyForCalls();
731
732 const auto id = view.id;
733 const auto i = _rows.find(id);
734 if (i == _rows.end()) {
735 const auto wrap = _wrap
736 ? _wrap.data()
737 : _initialWrap.data();
738 const auto [i, ok] = _rows.emplace(id, nullptr);
739 i->second.reset(wrap->insert(
740 0,
741 object_ptr<ProxyRow>(
742 wrap,
743 std::move(view))));
744 setupButtons(id, i->second.get());
745 if (_noRows) {
746 _noRows.reset();
747 }
748 wrap->resizeToWidth(width());
749 } else if (view.host.isEmpty()) {
750 _rows.erase(i);
751 } else {
752 i->second->updateFields(std::move(view));
753 }
754 }
755
createNoRowsLabel()756 void ProxiesBox::createNoRowsLabel() {
757 _noRows.reset(_wrap->add(
758 object_ptr<Ui::FixedHeightWidget>(
759 _wrap,
760 rowHeight()),
761 st::proxyEmptyListPadding));
762 _noRows->resize(
763 (st::boxWideWidth
764 - st::proxyEmptyListPadding.left()
765 - st::proxyEmptyListPadding.right()),
766 _noRows->height());
767 const auto label = Ui::CreateChild<Ui::FlatLabel>(
768 _noRows.get(),
769 tr::lng_proxy_description(tr::now),
770 st::proxyEmptyListLabel);
771 _noRows->widthValue(
772 ) | rpl::start_with_next([=](int width) {
773 label->resizeToWidth(width);
774 label->moveToLeft(0, 0);
775 }, label->lifetime());
776 }
777
setupButtons(int id,not_null<ProxyRow * > button)778 void ProxiesBox::setupButtons(int id, not_null<ProxyRow*> button) {
779 button->deleteClicks(
780 ) | rpl::start_with_next([=] {
781 _controller->deleteItem(id);
782 }, button->lifetime());
783
784 button->restoreClicks(
785 ) | rpl::start_with_next([=] {
786 _controller->restoreItem(id);
787 }, button->lifetime());
788
789 button->editClicks(
790 ) | rpl::start_with_next([=] {
791 getDelegate()->show(_controller->editItemBox(id));
792 }, button->lifetime());
793
794 button->shareClicks(
795 ) | rpl::start_with_next([=] {
796 _controller->shareItem(id);
797 }, button->lifetime());
798
799 button->clicks(
800 ) | rpl::start_with_next([=] {
801 _controller->applyItem(id);
802 }, button->lifetime());
803 }
804
ProxyBox(QWidget *,const ProxyData & data,Fn<void (ProxyData)> callback,Fn<void (ProxyData)> shareCallback)805 ProxyBox::ProxyBox(
806 QWidget*,
807 const ProxyData &data,
808 Fn<void(ProxyData)> callback,
809 Fn<void(ProxyData)> shareCallback)
810 : _callback(std::move(callback))
811 , _shareCallback(std::move(shareCallback))
812 , _content(this) {
813 setupControls(data);
814 }
815
prepare()816 void ProxyBox::prepare() {
817 setTitle(tr::lng_proxy_edit());
818
819 connect(_host.data(), &HostInput::changed, [=] {
820 Ui::PostponeCall(_host, [=] {
821 const auto host = _host->getLastText().trimmed();
822 static const auto mask = u"^\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d*)$"_q;
823 const auto match = QRegularExpression(mask).match(host);
824 if (_host->cursorPosition() == host.size()
825 && match.hasMatch()) {
826 const auto port = match.captured(1);
827 _port->setText(port);
828 _port->setCursorPosition(port.size());
829 _port->setFocus();
830 _host->setText(host.mid(0, host.size() - port.size() - 1));
831 }
832 });
833 });
834 _port.data()->events(
835 ) | rpl::start_with_next([=](not_null<QEvent*> e) {
836 if (e->type() == QEvent::KeyPress
837 && (static_cast<QKeyEvent*>(e.get())->key() == Qt::Key_Backspace)
838 && _port->cursorPosition() == 0) {
839 _host->setCursorPosition(_host->getLastText().size());
840 _host->setFocus();
841 }
842 }, _port->lifetime());
843
844 refreshButtons();
845 setDimensionsToContent(st::boxWideWidth, _content);
846 }
847
refreshButtons()848 void ProxyBox::refreshButtons() {
849 clearButtons();
850 addButton(tr::lng_settings_save(), [=] { save(); });
851 addButton(tr::lng_cancel(), [=] { closeBox(); });
852
853 const auto type = _type->value();
854 if (type == Type::Socks5 || type == Type::Mtproto) {
855 addLeftButton(tr::lng_proxy_share(), [=] { share(); });
856 }
857 }
858
save()859 void ProxyBox::save() {
860 if (const auto data = collectData()) {
861 _callback(data);
862 closeBox();
863 }
864 }
865
share()866 void ProxyBox::share() {
867 if (const auto data = collectData()) {
868 _shareCallback(data);
869 }
870 }
871
collectData()872 ProxyData ProxyBox::collectData() {
873 auto result = ProxyData();
874 result.type = _type->value();
875 result.host = _host->getLastText().trimmed();
876 result.port = _port->getLastText().trimmed().toInt();
877 result.user = (result.type == Type::Mtproto)
878 ? QString()
879 : _user->getLastText();
880 result.password = (result.type == Type::Mtproto)
881 ? _secret->getLastText()
882 : _password->getLastText();
883 if (result.host.isEmpty()) {
884 _host->showError();
885 } else if (!result.port) {
886 _port->showError();
887 } else if ((result.type == Type::Http || result.type == Type::Socks5)
888 && !result.password.isEmpty() && result.user.isEmpty()) {
889 _user->showError();
890 } else if (result.type == Type::Mtproto && !result.valid()) {
891 _secret->showError();
892 } else if (!result) {
893 _host->showError();
894 } else {
895 return result;
896 }
897 return ProxyData();
898 }
899
setupTypes()900 void ProxyBox::setupTypes() {
901 const auto types = std::map<Type, QString>{
902 { Type::Http, "HTTP" },
903 { Type::Socks5, "SOCKS5" },
904 { Type::Mtproto, "MTPROTO" },
905 };
906 for (const auto &[type, label] : types) {
907 _content->add(
908 object_ptr<Ui::Radioenum<Type>>(
909 _content,
910 _type,
911 type,
912 label),
913 st::proxyEditTypePadding);
914 }
915 _aboutSponsored = _content->add(object_ptr<Ui::SlideWrap<>>(
916 _content,
917 object_ptr<Ui::PaddingWrap<>>(
918 _content,
919 object_ptr<Ui::FlatLabel>(
920 _content,
921 tr::lng_proxy_sponsor_warning(tr::now),
922 st::boxDividerLabel),
923 st::proxyAboutSponsorPadding)));
924 }
925
setupSocketAddress(const ProxyData & data)926 void ProxyBox::setupSocketAddress(const ProxyData &data) {
927 addLabel(_content, tr::lng_proxy_address_label(tr::now));
928 const auto address = _content->add(
929 object_ptr<Ui::FixedHeightWidget>(
930 _content,
931 st::connectionHostInputField.heightMin),
932 st::proxyEditInputPadding);
933 _host = Ui::CreateChild<HostInput>(
934 address,
935 st::connectionHostInputField,
936 tr::lng_connection_host_ph(),
937 data.host);
938 _port = Ui::CreateChild<Ui::NumberInput>(
939 address,
940 st::connectionPortInputField,
941 tr::lng_connection_port_ph(),
942 data.port ? QString::number(data.port) : QString(),
943 65535);
944 address->widthValue(
945 ) | rpl::start_with_next([=](int width) {
946 _port->moveToRight(0, 0);
947 _host->resize(
948 width - _port->width() - st::proxyEditSkip,
949 _host->height());
950 _host->moveToLeft(0, 0);
951 }, address->lifetime());
952 }
953
setupCredentials(const ProxyData & data)954 void ProxyBox::setupCredentials(const ProxyData &data) {
955 _credentials = _content->add(
956 object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
957 _content,
958 object_ptr<Ui::VerticalLayout>(_content)));
959 const auto credentials = _credentials->entity();
960 addLabel(credentials, tr::lng_proxy_credentials_optional(tr::now));
961 _user = credentials->add(
962 object_ptr<Ui::InputField>(
963 credentials,
964 st::connectionUserInputField,
965 tr::lng_connection_user_ph(),
966 data.user),
967 st::proxyEditInputPadding);
968
969 auto passwordWrap = object_ptr<Ui::RpWidget>(credentials);
970 _password = Ui::CreateChild<Ui::PasswordInput>(
971 passwordWrap.data(),
972 st::connectionPasswordInputField,
973 tr::lng_connection_password_ph(),
974 (data.type == Type::Mtproto) ? QString() : data.password);
975 _password->move(0, 0);
976 _password->heightValue(
977 ) | rpl::start_with_next([=, wrap = passwordWrap.data()](int height) {
978 wrap->resize(wrap->width(), height);
979 }, _password->lifetime());
980 passwordWrap->widthValue(
981 ) | rpl::start_with_next([=](int width) {
982 _password->resize(width, _password->height());
983 }, _password->lifetime());
984 credentials->add(std::move(passwordWrap), st::proxyEditInputPadding);
985 }
986
setupMtprotoCredentials(const ProxyData & data)987 void ProxyBox::setupMtprotoCredentials(const ProxyData &data) {
988 _mtprotoCredentials = _content->add(
989 object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
990 _content,
991 object_ptr<Ui::VerticalLayout>(_content)));
992 const auto mtproto = _mtprotoCredentials->entity();
993 addLabel(mtproto, tr::lng_proxy_credentials(tr::now));
994
995 auto secretWrap = object_ptr<Ui::RpWidget>(mtproto);
996 _secret = Ui::CreateChild<Base64UrlInput>(
997 secretWrap.data(),
998 st::connectionUserInputField,
999 tr::lng_connection_proxy_secret_ph(),
1000 (data.type == Type::Mtproto) ? data.password : QString());
1001 _secret->move(0, 0);
1002 _secret->heightValue(
1003 ) | rpl::start_with_next([=, wrap = secretWrap.data()](int height) {
1004 wrap->resize(wrap->width(), height);
1005 }, _secret->lifetime());
1006 secretWrap->widthValue(
1007 ) | rpl::start_with_next([=](int width) {
1008 _secret->resize(width, _secret->height());
1009 }, _secret->lifetime());
1010 mtproto->add(std::move(secretWrap), st::proxyEditInputPadding);
1011 }
1012
setupControls(const ProxyData & data)1013 void ProxyBox::setupControls(const ProxyData &data) {
1014 _type = std::make_shared<Ui::RadioenumGroup<Type>>(
1015 (data.type == Type::None
1016 ? Type::Socks5
1017 : data.type));
1018 _content.create(this);
1019 _content->resizeToWidth(st::boxWideWidth);
1020 _content->moveToLeft(0, 0);
1021
1022 setupTypes();
1023 setupSocketAddress(data);
1024 setupCredentials(data);
1025 setupMtprotoCredentials(data);
1026
1027 const auto handleType = [=](Type type) {
1028 _credentials->toggle(
1029 type == Type::Http || type == Type::Socks5,
1030 anim::type::instant);
1031 _mtprotoCredentials->toggle(
1032 type == Type::Mtproto,
1033 anim::type::instant);
1034 _aboutSponsored->toggle(
1035 type == Type::Mtproto,
1036 anim::type::instant);
1037 };
1038 _type->setChangedCallback([=](Type type) {
1039 handleType(type);
1040 refreshButtons();
1041 });
1042 handleType(_type->value());
1043 }
1044
addLabel(not_null<Ui::VerticalLayout * > parent,const QString & text) const1045 void ProxyBox::addLabel(
1046 not_null<Ui::VerticalLayout*> parent,
1047 const QString &text) const {
1048 parent->add(
1049 object_ptr<Ui::FlatLabel>(
1050 parent,
1051 text,
1052 st::proxyEditTitle),
1053 st::proxyEditTitlePadding);
1054 }
1055
1056 } // namespace
1057
ProxiesBoxController(not_null<Main::Account * > account)1058 ProxiesBoxController::ProxiesBoxController(not_null<Main::Account*> account)
1059 : _account(account)
1060 , _settings(Core::App().settings().proxy())
1061 , _saveTimer([] { Local::writeSettings(); }) {
1062 _list = ranges::views::all(
1063 _settings.list()
__anonf7c060f62e02(const ProxyData &proxy) 1064 ) | ranges::views::transform([&](const ProxyData &proxy) {
1065 return Item{ ++_idCounter, proxy };
1066 }) | ranges::to_vector;
1067
1068 _settings.connectionTypeChanges(
__anonf7c060f62f02null1069 ) | rpl::start_with_next([=] {
1070 _proxySettingsChanges.fire_copy(_settings.settings());
1071 const auto i = findByProxy(_settings.selected());
1072 if (i != end(_list)) {
1073 updateView(*i);
1074 }
1075 }, _lifetime);
1076
1077 for (auto &item : _list) {
1078 refreshChecker(item);
1079 }
1080 }
1081
ShowApplyConfirmation(Type type,const QMap<QString,QString> & fields)1082 void ProxiesBoxController::ShowApplyConfirmation(
1083 Type type,
1084 const QMap<QString, QString> &fields) {
1085 const auto server = fields.value(qsl("server"));
1086 const auto port = fields.value(qsl("port")).toUInt();
1087 auto proxy = ProxyData();
1088 proxy.type = type;
1089 proxy.host = server;
1090 proxy.port = port;
1091 if (type == Type::Socks5) {
1092 proxy.user = fields.value(qsl("user"));
1093 proxy.password = fields.value(qsl("pass"));
1094 } else if (type == Type::Mtproto) {
1095 proxy.password = fields.value(qsl("secret"));
1096 }
1097 if (proxy) {
1098 const auto displayed = "https://" + server + "/";
1099 const auto parsed = QUrl::fromUserInput(displayed);
1100 const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed)
1101 ? displayed
1102 : parsed.isValid()
1103 ? QString::fromUtf8(parsed.toEncoded())
1104 : UrlClickHandler::ShowEncoded(displayed);
1105 const auto displayServer = QString(
1106 displayUrl
1107 ).replace(
1108 QRegularExpression(
1109 "^https://",
1110 QRegularExpression::CaseInsensitiveOption),
1111 QString()
1112 ).replace(QRegularExpression("/$"), QString());
1113 const auto text = tr::lng_sure_enable_socks(
1114 tr::now,
1115 lt_server,
1116 displayServer,
1117 lt_port,
1118 QString::number(port))
1119 + (proxy.type == Type::Mtproto
1120 ? "\n\n" + tr::lng_proxy_sponsor_warning(tr::now)
1121 : QString());
1122 auto callback = [=](Fn<void()> &&close) {
1123 auto &proxies = Core::App().settings().proxy().list();
1124 if (!ranges::contains(proxies, proxy)) {
1125 proxies.push_back(proxy);
1126 }
1127 Core::App().setCurrentProxy(
1128 proxy,
1129 ProxyData::Settings::Enabled);
1130 Local::writeSettings();
1131 close();
1132 };
1133 Ui::show(
1134 Box<Ui::ConfirmBox>(
1135 text,
1136 tr::lng_sure_enable(tr::now),
1137 std::move(callback)),
1138 Ui::LayerOption::KeepOther);
1139 } else {
1140 Ui::show(Box<Ui::InformBox>(
1141 (proxy.status() == ProxyData::Status::Unsupported
1142 ? tr::lng_proxy_unsupported(tr::now)
1143 : tr::lng_proxy_invalid(tr::now))));
1144 }
1145 }
1146
proxySettingsValue() const1147 auto ProxiesBoxController::proxySettingsValue() const
1148 -> rpl::producer<ProxyData::Settings> {
1149 return _proxySettingsChanges.events_starting_with_copy(
1150 _settings.settings()
1151 ) | rpl::distinct_until_changed();
1152 }
1153
refreshChecker(Item & item)1154 void ProxiesBoxController::refreshChecker(Item &item) {
1155 using Variants = MTP::DcOptions::Variants;
1156 const auto type = (item.data.type == Type::Http)
1157 ? Variants::Http
1158 : Variants::Tcp;
1159 const auto mtproto = &_account->mtp();
1160 const auto dcId = mtproto->mainDcId();
1161 const auto forFiles = false;
1162
1163 item.state = ItemState::Checking;
1164 const auto setup = [&](Checker &checker, const bytes::vector &secret) {
1165 checker = MTP::details::AbstractConnection::Create(
1166 mtproto,
1167 type,
1168 QThread::currentThread(),
1169 secret,
1170 item.data);
1171 setupChecker(item.id, checker);
1172 };
1173 if (item.data.type == Type::Mtproto) {
1174 const auto secret = item.data.secretFromMtprotoPassword();
1175 setup(item.checker, secret);
1176 item.checker->connectToServer(
1177 item.data.host,
1178 item.data.port,
1179 secret,
1180 dcId,
1181 forFiles);
1182 item.checkerv6 = nullptr;
1183 } else {
1184 const auto options = mtproto->dcOptions().lookup(
1185 dcId,
1186 MTP::DcType::Regular,
1187 true);
1188 const auto connect = [&](
1189 Checker &checker,
1190 Variants::Address address) {
1191 const auto &list = options.data[address][type];
1192 if (list.empty()
1193 || ((address == Variants::IPv6)
1194 && !Core::App().settings().proxy().tryIPv6())) {
1195 checker = nullptr;
1196 return;
1197 }
1198 const auto &endpoint = list.front();
1199 setup(checker, endpoint.secret);
1200 checker->connectToServer(
1201 QString::fromStdString(endpoint.ip),
1202 endpoint.port,
1203 endpoint.secret,
1204 dcId,
1205 forFiles);
1206 };
1207 connect(item.checker, Variants::IPv4);
1208 connect(item.checkerv6, Variants::IPv6);
1209 if (!item.checker && !item.checkerv6) {
1210 item.state = ItemState::Unavailable;
1211 }
1212 }
1213 }
1214
setupChecker(int id,const Checker & checker)1215 void ProxiesBoxController::setupChecker(int id, const Checker &checker) {
1216 using Connection = MTP::details::AbstractConnection;
1217 const auto pointer = checker.get();
1218 pointer->connect(pointer, &Connection::connected, [=] {
1219 const auto item = findById(id);
1220 const auto pingTime = pointer->pingTime();
1221 item->checker = nullptr;
1222 item->checkerv6 = nullptr;
1223 if (item->state == ItemState::Checking) {
1224 item->state = ItemState::Available;
1225 item->ping = pingTime;
1226 updateView(*item);
1227 }
1228 });
1229 const auto failed = [=] {
1230 const auto item = findById(id);
1231 if (item->checker == pointer) {
1232 item->checker = nullptr;
1233 } else if (item->checkerv6 == pointer) {
1234 item->checkerv6 = nullptr;
1235 }
1236 if (!item->checker
1237 && !item->checkerv6
1238 && item->state == ItemState::Checking) {
1239 item->state = ItemState::Unavailable;
1240 updateView(*item);
1241 }
1242 };
1243 pointer->connect(pointer, &Connection::disconnected, failed);
1244 pointer->connect(pointer, &Connection::error, failed);
1245 }
1246
CreateOwningBox(not_null<Main::Account * > account)1247 object_ptr<Ui::BoxContent> ProxiesBoxController::CreateOwningBox(
1248 not_null<Main::Account*> account) {
1249 auto controller = std::make_unique<ProxiesBoxController>(account);
1250 auto box = controller->create();
1251 Ui::AttachAsChild(box, std::move(controller));
1252 return box;
1253 }
1254
create()1255 object_ptr<Ui::BoxContent> ProxiesBoxController::create() {
1256 auto result = Box<ProxiesBox>(this, _settings);
1257 for (const auto &item : _list) {
1258 updateView(item);
1259 }
1260 return result;
1261 }
1262
findById(int id)1263 auto ProxiesBoxController::findById(int id) -> std::vector<Item>::iterator {
1264 const auto result = ranges::find(
1265 _list,
1266 id,
1267 [](const Item &item) { return item.id; });
1268 Assert(result != end(_list));
1269 return result;
1270 }
1271
findByProxy(const ProxyData & proxy)1272 auto ProxiesBoxController::findByProxy(const ProxyData &proxy)
1273 ->std::vector<Item>::iterator {
1274 return ranges::find(
1275 _list,
1276 proxy,
1277 [](const Item &item) { return item.data; });
1278 }
1279
deleteItem(int id)1280 void ProxiesBoxController::deleteItem(int id) {
1281 setDeleted(id, true);
1282 }
1283
restoreItem(int id)1284 void ProxiesBoxController::restoreItem(int id) {
1285 setDeleted(id, false);
1286 }
1287
shareItem(int id)1288 void ProxiesBoxController::shareItem(int id) {
1289 share(findById(id)->data);
1290 }
1291
applyItem(int id)1292 void ProxiesBoxController::applyItem(int id) {
1293 auto item = findById(id);
1294 if (_settings.isEnabled() && (_settings.selected() == item->data)) {
1295 return;
1296 } else if (item->deleted) {
1297 return;
1298 }
1299
1300 auto j = findByProxy(_settings.selected());
1301
1302 Core::App().setCurrentProxy(
1303 item->data,
1304 ProxyData::Settings::Enabled);
1305 saveDelayed();
1306
1307 if (j != end(_list)) {
1308 updateView(*j);
1309 }
1310 updateView(*item);
1311 }
1312
setDeleted(int id,bool deleted)1313 void ProxiesBoxController::setDeleted(int id, bool deleted) {
1314 auto item = findById(id);
1315 item->deleted = deleted;
1316
1317 if (deleted) {
1318 auto &proxies = _settings.list();
1319 proxies.erase(ranges::remove(proxies, item->data), end(proxies));
1320
1321 if (item->data == _settings.selected()) {
1322 _lastSelectedProxy = _settings.selected();
1323 _settings.setSelected(MTP::ProxyData());
1324 if (_settings.isEnabled()) {
1325 _lastSelectedProxyUsed = true;
1326 Core::App().setCurrentProxy(
1327 ProxyData(),
1328 ProxyData::Settings::System);
1329 saveDelayed();
1330 } else {
1331 _lastSelectedProxyUsed = false;
1332 }
1333 }
1334 } else {
1335 auto &proxies = _settings.list();
1336 if (ranges::find(proxies, item->data) == end(proxies)) {
1337 auto insertBefore = item + 1;
1338 while (insertBefore != end(_list) && insertBefore->deleted) {
1339 ++insertBefore;
1340 }
1341 auto insertBeforeIt = (insertBefore == end(_list))
1342 ? end(proxies)
1343 : ranges::find(proxies, insertBefore->data);
1344 proxies.insert(insertBeforeIt, item->data);
1345 }
1346
1347 if (!_settings.selected() && _lastSelectedProxy == item->data) {
1348 Assert(!_settings.isEnabled());
1349
1350 if (base::take(_lastSelectedProxyUsed)) {
1351 Core::App().setCurrentProxy(
1352 base::take(_lastSelectedProxy),
1353 ProxyData::Settings::Enabled);
1354 } else {
1355 _settings.setSelected(base::take(_lastSelectedProxy));
1356 }
1357 }
1358 }
1359 saveDelayed();
1360 updateView(*item);
1361 }
1362
editItemBox(int id)1363 object_ptr<Ui::BoxContent> ProxiesBoxController::editItemBox(int id) {
1364 return Box<ProxyBox>(findById(id)->data, [=](const ProxyData &result) {
1365 auto i = findById(id);
1366 auto j = ranges::find(
1367 _list,
1368 result,
1369 [](const Item &item) { return item.data; });
1370 if (j != end(_list) && j != i) {
1371 replaceItemWith(i, j);
1372 } else {
1373 replaceItemValue(i, result);
1374 }
1375 }, [=](const ProxyData &proxy) {
1376 share(proxy);
1377 });
1378 }
1379
replaceItemWith(std::vector<Item>::iterator which,std::vector<Item>::iterator with)1380 void ProxiesBoxController::replaceItemWith(
1381 std::vector<Item>::iterator which,
1382 std::vector<Item>::iterator with) {
1383 auto &proxies = _settings.list();
1384 proxies.erase(ranges::remove(proxies, which->data), end(proxies));
1385
1386 _views.fire({ which->id });
1387 _list.erase(which);
1388
1389 if (with->deleted) {
1390 restoreItem(with->id);
1391 }
1392 applyItem(with->id);
1393 saveDelayed();
1394 }
1395
replaceItemValue(std::vector<Item>::iterator which,const ProxyData & proxy)1396 void ProxiesBoxController::replaceItemValue(
1397 std::vector<Item>::iterator which,
1398 const ProxyData &proxy) {
1399 if (which->deleted) {
1400 restoreItem(which->id);
1401 }
1402
1403 auto &proxies = _settings.list();
1404 const auto i = ranges::find(proxies, which->data);
1405 Assert(i != end(proxies));
1406 *i = proxy;
1407 which->data = proxy;
1408 refreshChecker(*which);
1409
1410 applyItem(which->id);
1411 saveDelayed();
1412 }
1413
addNewItemBox()1414 object_ptr<Ui::BoxContent> ProxiesBoxController::addNewItemBox() {
1415 return Box<ProxyBox>(ProxyData(), [=](const ProxyData &result) {
1416 auto j = ranges::find(
1417 _list,
1418 result,
1419 [](const Item &item) { return item.data; });
1420 if (j != end(_list)) {
1421 if (j->deleted) {
1422 restoreItem(j->id);
1423 }
1424 applyItem(j->id);
1425 } else {
1426 addNewItem(result);
1427 }
1428 }, [=](const ProxyData &proxy) {
1429 share(proxy);
1430 });
1431 }
1432
addNewItem(const ProxyData & proxy)1433 void ProxiesBoxController::addNewItem(const ProxyData &proxy) {
1434 auto &proxies = _settings.list();
1435 proxies.push_back(proxy);
1436
1437 _list.push_back({ ++_idCounter, proxy });
1438 refreshChecker(_list.back());
1439 applyItem(_list.back().id);
1440 }
1441
setProxySettings(ProxyData::Settings value)1442 bool ProxiesBoxController::setProxySettings(ProxyData::Settings value) {
1443 if (_settings.settings() == value) {
1444 return true;
1445 } else if (value == ProxyData::Settings::Enabled) {
1446 if (_settings.list().empty()) {
1447 return false;
1448 } else if (!_settings.selected()) {
1449 _settings.setSelected(_settings.list().back());
1450 auto j = findByProxy(_settings.selected());
1451 if (j != end(_list)) {
1452 updateView(*j);
1453 }
1454 }
1455 }
1456 Core::App().setCurrentProxy(_settings.selected(), value);
1457 saveDelayed();
1458 return true;
1459 }
1460
setProxyForCalls(bool enabled)1461 void ProxiesBoxController::setProxyForCalls(bool enabled) {
1462 if (_settings.useProxyForCalls() == enabled) {
1463 return;
1464 }
1465 _settings.setUseProxyForCalls(enabled);
1466 if (_settings.isEnabled() && _settings.selected().supportsCalls()) {
1467 _settings.connectionTypeChangesNotify();
1468 }
1469 saveDelayed();
1470 }
1471
setTryIPv6(bool enabled)1472 void ProxiesBoxController::setTryIPv6(bool enabled) {
1473 if (Core::App().settings().proxy().tryIPv6() == enabled) {
1474 return;
1475 }
1476 Core::App().settings().proxy().setTryIPv6(enabled);
1477 _account->mtp().restart();
1478 _settings.connectionTypeChangesNotify();
1479 saveDelayed();
1480 }
1481
saveDelayed()1482 void ProxiesBoxController::saveDelayed() {
1483 _saveTimer.callOnce(kSaveSettingsDelayedTimeout);
1484 }
1485
views() const1486 auto ProxiesBoxController::views() const -> rpl::producer<ItemView> {
1487 return _views.events();
1488 }
1489
updateView(const Item & item)1490 void ProxiesBoxController::updateView(const Item &item) {
1491 const auto selected = (_settings.selected() == item.data);
1492 const auto deleted = item.deleted;
1493 const auto type = [&] {
1494 switch (item.data.type) {
1495 case Type::Http:
1496 return qsl("HTTP");
1497 case Type::Socks5:
1498 return qsl("SOCKS5");
1499 case Type::Mtproto:
1500 return qsl("MTPROTO");
1501 }
1502 Unexpected("Proxy type in ProxiesBoxController::updateView.");
1503 }();
1504 const auto state = [&] {
1505 if (!selected || !_settings.isEnabled()) {
1506 return item.state;
1507 } else if (_account->mtp().dcstate() == MTP::ConnectedState) {
1508 return ItemState::Online;
1509 }
1510 return ItemState::Connecting;
1511 }();
1512 const auto supportsShare = (item.data.type == Type::Socks5)
1513 || (item.data.type == Type::Mtproto);
1514 const auto supportsCalls = item.data.supportsCalls();
1515 _views.fire({
1516 item.id,
1517 type,
1518 item.data.host,
1519 item.data.port,
1520 item.ping,
1521 !deleted && selected,
1522 deleted,
1523 !deleted && supportsShare,
1524 supportsCalls,
1525 state });
1526 }
1527
share(const ProxyData & proxy)1528 void ProxiesBoxController::share(const ProxyData &proxy) {
1529 if (proxy.type == Type::Http) {
1530 return;
1531 }
1532 const auto link = qsl("https://t.me/")
1533 + (proxy.type == Type::Socks5 ? "socks" : "proxy")
1534 + "?server=" + proxy.host + "&port=" + QString::number(proxy.port)
1535 + ((proxy.type == Type::Socks5 && !proxy.user.isEmpty())
1536 ? "&user=" + qthelp::url_encode(proxy.user) : "")
1537 + ((proxy.type == Type::Socks5 && !proxy.password.isEmpty())
1538 ? "&pass=" + qthelp::url_encode(proxy.password) : "")
1539 + ((proxy.type == Type::Mtproto && !proxy.password.isEmpty())
1540 ? "&secret=" + proxy.password : "");
1541 QGuiApplication::clipboard()->setText(link);
1542 Ui::Toast::Show(tr::lng_username_copied(tr::now));
1543 }
1544
~ProxiesBoxController()1545 ProxiesBoxController::~ProxiesBoxController() {
1546 if (_saveTimer.isActive()) {
1547 base::call_delayed(
1548 kSaveSettingsDelayedTimeout,
1549 QCoreApplication::instance(),
1550 [] { Local::writeSettings(); });
1551 }
1552 }
1553