From 8bbea976ea0cda58c000b587592a1d96377e56d3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 28 Apr 2018 13:27:43 +0400 Subject: [PATCH] Display proxies list in a box. --- Telegram/Resources/langs/lang.strings | 16 + Telegram/SourceFiles/boxes/boxes.style | 34 ++ Telegram/SourceFiles/boxes/connection_box.cpp | 416 ++++++++++++++++++ Telegram/SourceFiles/boxes/connection_box.h | 52 +++ Telegram/SourceFiles/mainwindow.cpp | 3 +- .../settings/settings_advanced_widget.cpp | 3 +- 6 files changed, 522 insertions(+), 2 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 3759cab2c..3476de19b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -418,8 +418,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_connection_port_ph" = "Port"; "lng_connection_user_ph" = "Username"; "lng_connection_password_ph" = "Password"; +"lng_connection_proxy_secret_ph" = "Secret"; "lng_connection_save" = "Save"; +"lng_proxy_settings" = "Proxy settings"; +"lng_proxy_use" = "Use proxy"; +"lng_proxy_add" = "Add proxy"; +"lng_proxy_online" = "online"; +"lng_proxy_checking" = "checking"; +"lng_proxy_connecting" = "connecting"; +"lng_proxy_available" = "available"; +"lng_proxy_unavailable" = "unavailable"; +"lng_proxy_edit" = "Edit proxy"; +"lng_proxy_undo_delete" = "Undo"; +"lng_proxy_type_http" = "HTTP"; +"lng_proxy_type_socks5" = "SOCKS5"; +"lng_proxy_type_mtproto" = "MTPROTO"; +"lng_proxy_edit_share" = "Share"; + "lng_settings_blocked_users" = "Blocked users"; "lng_settings_last_seen_privacy" = "Last seen privacy"; "lng_settings_calls_privacy" = "Phone calls privacy"; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 7c1715fe6..6bf1604cd 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -711,3 +711,37 @@ sendMediaFileThumbSize: 64px; sendMediaFileThumbSkip: 10px; sendMediaFileNameTop: 7px; sendMediaFileStatusTop: 37px; + +proxyRowPadding: margins(22px, 8px, 8px, 8px); +proxyRowIconSkip: 16px; +proxyRowSkip: 2px; +proxyRowRipple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; +} +proxyRowSelectedIcon: icon {{ "mediaview_save_check", windowActiveTextFg }}; +proxyRowTitleFg: windowFg; +proxyRowTitlePalette: TextPalette(defaultTextPalette) { + linkFg: windowSubTextFg; +} +proxyRowTitleStyle: TextStyle(defaultTextStyle) { + font: semiboldFont; + linkFont: normalFont; + linkFontOver: normalFont; +} +proxyRowStatusFg: windowSubTextFg; +proxyRowStatusFgOnline: windowActiveTextFg; +proxyRowStatusFgOffline: attentionButtonFg; +proxyRowEdit: IconButton(defaultIconButton) { + width: 40px; + height: 40px; + + icon: icon {{ "settings_edit_name", menuIconFg }}; + iconOver: icon {{ "settings_edit_name", menuIconFgOver }}; + iconPosition: point(12px, 13px); + + rippleAreaSize: 40px; + rippleAreaPosition: point(0px, 0px); + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } +} diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index d8e1dd061..b3eb07362 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -19,9 +19,307 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" +#include "ui/wrap/fade_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "ui/text_options.h" #include "history/history_location_manager.h" #include "application.h" #include "styles/style_boxes.h" +#include "styles/style_chat_helpers.h" + +namespace { + +class ProxyRow : public Ui::RippleButton { +public: + using View = ProxiesBoxController::ItemView; + + ProxyRow(QWidget *parent, View &&view); + + void updateFields(View &&view); + + rpl::producer<> deleteClicks() const; + rpl::producer<> restoreClicks() const; + rpl::producer<> editClicks() const; + +protected: + int resizeGetHeight(int newWidth) override; + + void paintEvent(QPaintEvent *e) override; + +private: + void setupControls(View &&view); + int countAvailableWidth() const; + + View _view; + + Text _title; + object_ptr> _edit; + object_ptr> _delete; + object_ptr> _restore; + int _skipLeft = 0; + int _skipRight = 0; + +}; + +class ProxiesBox : public BoxContent { +public: + using View = ProxiesBoxController::ItemView; + + ProxiesBox(QWidget*, not_null controller); + +protected: + void prepare() override; + +private: + void setupContent(); + void applyView(View &&view); + void setupButtons(int id, not_null button); + + not_null _controller; + object_ptr _initialInner; + QPointer _inner; + base::flat_map> _rows; + +}; + +class ProxyBox : public BoxContent { +public: + ProxyBox( + QWidget*, + const ProxyData &data, + base::lambda callback); + +protected: + void prepare() override; + +private: + base::lambda _callback; + +}; + +ProxyRow::ProxyRow(QWidget *parent, View &&view) +: RippleButton(parent, st::proxyRowRipple) +, _edit(this, object_ptr(this, st::proxyRowEdit)) +, _delete(this, object_ptr(this, st::stickersRemove)) +, _restore( + this, + object_ptr( + this, + langFactory(lng_proxy_undo_delete), + st::stickersUndoRemove)) { + setupControls(std::move(view)); +} + +rpl::producer<> ProxyRow::deleteClicks() const { + return _delete->entity()->clicks(); +} + +rpl::producer<> ProxyRow::restoreClicks() const { + return _restore->entity()->clicks(); +} + +rpl::producer<> ProxyRow::editClicks() const { + return _edit->entity()->clicks(); +} + +void ProxyRow::setupControls(View &&view) { + updateFields(std::move(view)); + + _delete->finishAnimating(); + _restore->finishAnimating(); + _edit->finishAnimating(); +} + +int ProxyRow::countAvailableWidth() const { + return width() - _skipLeft - _skipRight; +} + +void ProxyRow::updateFields(View &&view) { + _view = std::move(view); + const auto endpoint = _view.host + ':' + QString::number(_view.port); + _title.setText( + st::proxyRowTitleStyle, + _view.type + ' ' + textcmdLink(1, endpoint), + Ui::ItemTextDefaultOptions()); + _delete->toggle(!_view.deleted, anim::type::instant); + _edit->toggle(!_view.deleted, anim::type::instant); + _restore->toggle(_view.deleted, anim::type::instant); + + update(); +} + +int ProxyRow::resizeGetHeight(int newWidth) { + const auto result = st::proxyRowPadding.top() + + st::semiboldFont->height + + st::proxyRowSkip + + st::normalFont->height + + st::proxyRowPadding.bottom(); + auto right = st::proxyRowPadding.right(); + _delete->moveToRight( + right, + (result - _delete->height()) / 2, + newWidth); + _restore->moveToRight( + right, + (result - _restore->height()) / 2, + newWidth); + right += _delete->width(); + _edit->moveToRight( + right, + (result - _edit->height()) / 2, + newWidth); + right -= _edit->width(); + _skipRight = right; + _skipLeft = st::proxyRowPadding.left() + + st::proxyRowSelectedIcon.width() + + st::proxyRowIconSkip; + return result; +} + +void ProxyRow::paintEvent(QPaintEvent *e) { + Painter p(this); + + if (!_view.deleted) { + const auto ms = getms(); + paintRipple(p, 0, 0, ms); + } + + const auto left = _skipLeft; + const auto availableWidth = countAvailableWidth(); + auto top = st::proxyRowPadding.top(); + + if (_view.deleted) { + p.setOpacity(st::stickersRowDisabledOpacity); + } else if (_view.selected) { + st::proxyRowSelectedIcon.paint( + p, + st::proxyRowPadding.left(), + (height() - st::proxyRowSelectedIcon.height()) / 2, + width()); + } + + p.setPen(st::proxyRowTitleFg); + p.setFont(st::semiboldFont); + p.setTextPalette(st::proxyRowTitlePalette); + _title.drawLeftElided(p, left, top, availableWidth, width()); + top += st::semiboldFont->height + st::proxyRowSkip; + + const auto statusFg = [&] { + switch (_view.state) { + case View::State::Online: + return st::proxyRowStatusFgOnline; + case View::State::Unavailable: + return st::proxyRowStatusFgOffline; + default: + return st::proxyRowStatusFg; + } + }(); + const auto status = [&] { + switch (_view.state) { + case View::State::Available: + return lang(lng_proxy_available); + case View::State::Checking: + return lang(lng_proxy_available); + case View::State::Connecting: + return lang(lng_proxy_connecting); + case View::State::Online: + return lang(lng_proxy_online); + case View::State::Unavailable: + return lang(lng_proxy_unavailable); + } + Unexpected("State in ProxyRow::paintEvent."); + }(); + p.setPen(statusFg); + p.setFont(st::normalFont); + p.drawTextLeft(left, top, width(), status); + top += st::normalFont->height + st::proxyRowPadding.bottom(); + +} + +ProxiesBox::ProxiesBox( + QWidget*, + not_null controller) +: _controller(controller) +, _initialInner(this) { + _controller->views( + ) | rpl::start_with_next([=](View &&view) { + applyView(std::move(view)); + }, lifetime()); +} + +void ProxiesBox::prepare() { + setTitle(langFactory(lng_proxy_settings)); + + addButton(langFactory(lng_proxy_add), [=] { + Ui::show(_controller->addNewItemBox(), LayerOption::KeepOther); + }); + addButton(langFactory(lng_close), [=] { + closeBox(); + }); + + setupContent(); +} + +void ProxiesBox::setupContent() { + _inner = setInnerWidget(std::move(_initialInner)); + + _inner->resizeToWidth(st::boxWideWidth); + + _inner->heightValue( + ) | rpl::map([](int height) { + return std::min(height, st::boxMaxListHeight); + }) | rpl::distinct_until_changed( + ) | rpl::start_with_next([=](int height) { + setDimensions(st::boxWideWidth, height); + }, lifetime()); +} + +void ProxiesBox::applyView(View &&view) { + const auto id = view.id; + const auto i = _rows.find(id); + if (i == _rows.end()) { + const auto inner = _inner + ? _inner.data() + : _initialInner.data(); + const auto [i, ok] = _rows.emplace(id, inner->add( + object_ptr( + inner, + std::move(view)))); + setupButtons(id, i->second); + } else { + i->second->updateFields(std::move(view)); + } +} + +void ProxiesBox::setupButtons(int id, not_null button) { + button->deleteClicks( + ) | rpl::start_with_next([=] { + _controller->deleteItem(id); + }, button->lifetime()); + + button->restoreClicks( + ) | rpl::start_with_next([=] { + _controller->restoreItem(id); + }, button->lifetime()); + + button->editClicks( + ) | rpl::start_with_next([=] { + Ui::show(_controller->editItemBox(id), LayerOption::KeepOther); + }, button->lifetime()); +} + +ProxyBox::ProxyBox( + QWidget*, + const ProxyData &data, + base::lambda callback) +: _callback(std::move(callback)) { +} + +void ProxyBox::prepare() { + +} + +} // namespace void ConnectionBox::ShowApplyProxyConfirmation( ProxyData::Type type, @@ -363,3 +661,121 @@ void AutoDownloadBox::onSave() { } closeBox(); } + +ProxiesBoxController::ProxiesBoxController() { + _list = ranges::view::all( + Global::ProxiesList() + ) | ranges::view::transform([&](const ProxyData &proxy) { + return Item{ ++_idCounter, proxy }; + }) | ranges::to_vector; +} + +object_ptr ProxiesBoxController::CreateOwningBox() { + auto controller = std::make_unique(); + auto box = controller->create(); + Ui::AttachAsChild(box, std::move(controller)); + return box; +} + +object_ptr ProxiesBoxController::create() { + auto result = Box(this); + for (const auto &item : _list) { + updateView(item); + } + return std::move(result); +} + +auto ProxiesBoxController::findById(int id) -> std::vector::iterator { + const auto result = ranges::find( + _list, + id, + [](const Item &item) { return item.id; }); + Assert(result != end(_list)); + return result; +} + +void ProxiesBoxController::deleteItem(int id) { + setDeleted(id, true); +} + +void ProxiesBoxController::restoreItem(int id) { + setDeleted(id, false); +} + +void ProxiesBoxController::setDeleted(int id, bool deleted) { + auto item = findById(id); + item->deleted = deleted; + updateView(*item); +} + +object_ptr ProxiesBoxController::editItemBox(int id) { + return Box(findById(id)->data, [=](const ProxyData &result) { + auto i = findById(id); + auto j = ranges::find( + _list, + result, + [](const Item &item) { return item.data; }); + if (j != end(_list) && j != i) { + _views.fire({ i->id }); + _list.erase(i); + if (j->deleted) { + restoreItem(j->id); + } + } else { + i->data = result; + if (i->deleted) { + restoreItem(i->id); + } else { + updateView(*i); + } + } + }); +} + +object_ptr ProxiesBoxController::addNewItemBox() { + return Box(ProxyData(), [=](const ProxyData &result) { + auto j = ranges::find( + _list, + result, + [](const Item &item) { return item.data; }); + if (j != end(_list)) { + if (j->deleted) { + restoreItem(j->id); + } + } else { + _list.push_back({ ++_idCounter, result }); + updateView(_list.back()); + } + }); +} + +auto ProxiesBoxController::views() const -> rpl::producer { + return _views.events(); +} + +void ProxiesBoxController::updateView(const Item &item) { + const auto state = ItemView::State::Checking; + const auto ping = 0; + const auto selected = (Global::SelectedProxy() == item.data); + const auto deleted = item.deleted; + const auto type = [&] { + switch (item.data.type) { + case ProxyData::Type::Http: + return qsl("HTTP"); + case ProxyData::Type::Socks5: + return qsl("SOCKS5"); + case ProxyData::Type::Mtproto: + return qsl("MTPROTO"); + } + Unexpected("Proxy type in ProxiesBoxController::updateView."); + }(); + _views.fire({ + item.id, + type, + item.data.host, + item.data.port, + ping, + !deleted && selected, + deleted, + state }); +} diff --git a/Telegram/SourceFiles/boxes/connection_box.h b/Telegram/SourceFiles/boxes/connection_box.h index 7e08955ad..08fa3b3d0 100644 --- a/Telegram/SourceFiles/boxes/connection_box.h +++ b/Telegram/SourceFiles/boxes/connection_box.h @@ -89,3 +89,55 @@ private: int _sectionHeight = 0; }; + +class ProxiesBoxController { +public: + ProxiesBoxController(); + + static object_ptr CreateOwningBox(); + object_ptr create(); + + struct ItemView { + enum class State { + Connecting, + Online, + Checking, + Available, + Unavailable + }; + + int id = 0; + QString type; + QString host; + uint32 port = 0; + int ping = 0; + bool selected = false; + bool deleted = false; + State state = State::Checking; + + }; + + void deleteItem(int id); + void restoreItem(int id); + object_ptr editItemBox(int id); + object_ptr addNewItemBox(); + + rpl::producer views() const; + +private: + struct Item { + int id = 0; + ProxyData data; + bool deleted = false; + }; + + std::vector::iterator findById(int id); + void setDeleted(int id, bool deleted); + void updateView(const Item &item); + + int _idCounter = 0; + int _selected = -1; + std::vector _list; + rpl::event_stream _views; + +}; diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index 775d292c4..0c4dd3d5d 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -100,7 +100,8 @@ void ConnectingWidget::paintEvent(QPaintEvent *e) { void ConnectingWidget::onReconnect() { if (Global::UseProxy()) { - Ui::show(Box()); + //Ui::show(Box()); + Ui::show(ProxiesBoxController::CreateOwningBox()); } else { MTP::restart(); } diff --git a/Telegram/SourceFiles/settings/settings_advanced_widget.cpp b/Telegram/SourceFiles/settings/settings_advanced_widget.cpp index abd53942e..5e2531e3f 100644 --- a/Telegram/SourceFiles/settings/settings_advanced_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_advanced_widget.cpp @@ -111,7 +111,8 @@ void AdvancedWidget::connectionTypeUpdated() { } void AdvancedWidget::onConnectionType() { - Ui::show(Box()); + //Ui::show(Box()); + Ui::show(ProxiesBoxController::CreateOwningBox()); } #endif // !TDESKTOP_DISABLE_NETWORK_PROXY