Display proxies list in a box.

This commit is contained in:
John Preston 2018-04-28 13:27:43 +04:00
parent 900d1ddb36
commit 8bbea976ea
6 changed files with 522 additions and 2 deletions

View File

@ -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";

View File

@ -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;
}
}

View File

@ -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<Ui::FadeWrapScaled<Ui::IconButton>> _edit;
object_ptr<Ui::FadeWrapScaled<Ui::IconButton>> _delete;
object_ptr<Ui::FadeWrapScaled<Ui::RoundButton>> _restore;
int _skipLeft = 0;
int _skipRight = 0;
};
class ProxiesBox : public BoxContent {
public:
using View = ProxiesBoxController::ItemView;
ProxiesBox(QWidget*, not_null<ProxiesBoxController*> controller);
protected:
void prepare() override;
private:
void setupContent();
void applyView(View &&view);
void setupButtons(int id, not_null<ProxyRow*> button);
not_null<ProxiesBoxController*> _controller;
object_ptr<Ui::VerticalLayout> _initialInner;
QPointer<Ui::VerticalLayout> _inner;
base::flat_map<int, QPointer<ProxyRow>> _rows;
};
class ProxyBox : public BoxContent {
public:
ProxyBox(
QWidget*,
const ProxyData &data,
base::lambda<void(ProxyData)> callback);
protected:
void prepare() override;
private:
base::lambda<void(ProxyData)> _callback;
};
ProxyRow::ProxyRow(QWidget *parent, View &&view)
: RippleButton(parent, st::proxyRowRipple)
, _edit(this, object_ptr<Ui::IconButton>(this, st::proxyRowEdit))
, _delete(this, object_ptr<Ui::IconButton>(this, st::stickersRemove))
, _restore(
this,
object_ptr<Ui::RoundButton>(
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<ProxiesBoxController*> 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<ProxyRow>(
inner,
std::move(view))));
setupButtons(id, i->second);
} else {
i->second->updateFields(std::move(view));
}
}
void ProxiesBox::setupButtons(int id, not_null<ProxyRow*> 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<void(ProxyData)> 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<BoxContent> ProxiesBoxController::CreateOwningBox() {
auto controller = std::make_unique<ProxiesBoxController>();
auto box = controller->create();
Ui::AttachAsChild(box, std::move(controller));
return box;
}
object_ptr<BoxContent> ProxiesBoxController::create() {
auto result = Box<ProxiesBox>(this);
for (const auto &item : _list) {
updateView(item);
}
return std::move(result);
}
auto ProxiesBoxController::findById(int id) -> std::vector<Item>::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<BoxContent> ProxiesBoxController::editItemBox(int id) {
return Box<ProxyBox>(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<BoxContent> ProxiesBoxController::addNewItemBox() {
return Box<ProxyBox>(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<ItemView> {
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 });
}

View File

@ -89,3 +89,55 @@ private:
int _sectionHeight = 0;
};
class ProxiesBoxController {
public:
ProxiesBoxController();
static object_ptr<BoxContent> CreateOwningBox();
object_ptr<BoxContent> 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<BoxContent> editItemBox(int id);
object_ptr<BoxContent> addNewItemBox();
rpl::producer<ItemView> views() const;
private:
struct Item {
int id = 0;
ProxyData data;
bool deleted = false;
};
std::vector<Item>::iterator findById(int id);
void setDeleted(int id, bool deleted);
void updateView(const Item &item);
int _idCounter = 0;
int _selected = -1;
std::vector<Item> _list;
rpl::event_stream<ItemView> _views;
};

View File

@ -100,7 +100,8 @@ void ConnectingWidget::paintEvent(QPaintEvent *e) {
void ConnectingWidget::onReconnect() {
if (Global::UseProxy()) {
Ui::show(Box<ConnectionBox>());
//Ui::show(Box<ConnectionBox>());
Ui::show(ProxiesBoxController::CreateOwningBox());
} else {
MTP::restart();
}

View File

@ -111,7 +111,8 @@ void AdvancedWidget::connectionTypeUpdated() {
}
void AdvancedWidget::onConnectionType() {
Ui::show(Box<ConnectionBox>());
//Ui::show(Box<ConnectionBox>());
Ui::show(ProxiesBoxController::CreateOwningBox());
}
#endif // !TDESKTOP_DISABLE_NETWORK_PROXY