Create and edit proxy box.

This commit is contained in:
John Preston 2018-04-28 19:58:22 +04:00
parent a7c77682d7
commit 9935a36c3d
7 changed files with 433 additions and 23 deletions

View File

@ -424,6 +424,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_proxy_settings" = "Proxy settings";
"lng_proxy_use" = "Use proxy";
"lng_proxy_add" = "Add proxy";
"lng_proxy_share" = "Share";
"lng_proxy_online" = "online";
"lng_proxy_checking" = "checking";
"lng_proxy_connecting" = "connecting";
@ -431,10 +432,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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_address_label" = "Socket address";
"lng_proxy_credentials_optional" = "Credentials (optional)";
"lng_proxy_credentials" = "Credentials";
"lng_proxy_edit_share" = "Share";
"lng_proxy_description" = "Your saved proxy list will be here.";
"lng_settings_blocked_users" = "Blocked users";
"lng_settings_last_seen_privacy" = "Last seen privacy";

View File

@ -747,3 +747,20 @@ proxyRowEdit: IconButton(defaultIconButton) {
color: windowBgOver;
}
}
proxyEditTitle: FlatLabel(defaultFlatLabel) {
style: TextStyle(defaultTextStyle) {
font: autoDownloadTitleFont;
}
textFg: boxTitleFg;
}
proxyEditTitlePadding: margins(22px, 16px, 22px, 0px);
proxyEditTypePadding: margins(22px, 4px, 22px, 8px);
proxyEditInputPadding: margins(22px, 0px, 22px, 0px);
proxyEditSkip: 16px;
proxyEmptyListLabel: FlatLabel(defaultFlatLabel) {
align: align(top);
textFg: windowSubTextFg;
}
proxyEmptyListPadding: margins(22px, 48px, 22px, 0px);

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/confirm_box.h"
#include "lang/lang_keys.h"
#include "storage/localstorage.h"
#include "base/qthelp_url.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "auth_session.h"
@ -19,9 +20,12 @@ 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/widgets/labels.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/toast/toast.h"
#include "ui/text_options.h"
#include "history/history_location_manager.h"
#include "application.h"
@ -75,17 +79,20 @@ protected:
private:
void setupContent();
void createNoRowsLabel();
void addNewProxy();
void applyView(View &&view);
void setupButtons(int id, not_null<ProxyRow*> button);
int rowHeight() const;
not_null<ProxiesBoxController*> _controller;
object_ptr<Ui::PaddingWrap<Ui::Checkbox>> _useProxy;
object_ptr<Ui::PaddingWrap<Ui::Checkbox>> _tryIPv6;
base::unique_qptr<Ui::RpWidget> _noRows;
object_ptr<Ui::VerticalLayout> _initialWrap;
QPointer<Ui::VerticalLayout> _wrap;
base::flat_map<int, QPointer<ProxyRow>> _rows;
base::flat_map<int, base::unique_qptr<ProxyRow>> _rows;
};
@ -100,8 +107,37 @@ protected:
void prepare() override;
private:
using Type = ProxyData::Type;
void refreshButtons();
ProxyData collectData();
void save();
void share();
void setupControls(const ProxyData &data);
void setupTypes();
void setupSocketAddress(const ProxyData &data);
void setupCredentials(const ProxyData &data);
void setupMtprotoCredentials(const ProxyData &data);
void addLabel(
not_null<Ui::VerticalLayout*> parent,
const QString &text) const;
base::lambda<void(ProxyData)> _callback;
object_ptr<Ui::VerticalLayout> _content;
std::shared_ptr<Ui::RadioenumGroup<Type>> _type;
QPointer<Ui::InputField> _host;
QPointer<Ui::PortInput> _port;
QPointer<Ui::InputField> _user;
QPointer<Ui::PasswordInput> _password;
QPointer<Ui::HexInput> _secret;
QPointer<Ui::SlideWrap<Ui::VerticalLayout>> _credentials;
QPointer<Ui::SlideWrap<Ui::VerticalLayout>> _mtprotoCredentials;
};
ProxyRow::ProxyRow(QWidget *parent, View &&view)
@ -283,9 +319,13 @@ void ProxiesBox::setupContent() {
_useProxy->moveToLeft(0, 0);
subscribe(_useProxy->entity()->checkedChanged, [=](bool checked) {
if (!_controller->setProxyEnabled(checked)) {
_useProxy->entity()->setChecked(false);
addNewProxy();
}
});
subscribe(_tryIPv6->entity()->checkedChanged, [=](bool checked) {
_controller->setTryIPv6(checked);
});
subscribe(Global::RefConnectionTypeChanged(), [=] {
_useProxy->entity()->setChecked(Global::UseProxy());
});
@ -306,12 +346,16 @@ void ProxiesBox::setupContent() {
inner,
st::proxyRowPadding.bottom()));
if (_rows.empty()) {
createNoRowsLabel();
}
inner->resizeToWidth(st::boxWideWidth);
inner->heightValue(
) | rpl::map([=](int height) {
return std::min(
topSkip + height + bottomSkip,
topSkip + std::max(height, 3 * rowHeight()) + bottomSkip,
st::boxMaxListHeight);
}) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](int height) {
@ -324,6 +368,14 @@ void ProxiesBox::setupContent() {
}, _tryIPv6->lifetime());
}
int ProxiesBox::rowHeight() const {
return st::proxyRowPadding.top()
+ st::semiboldFont->height
+ st::proxyRowSkip
+ st::normalFont->height
+ st::proxyRowPadding.bottom();
}
void ProxiesBox::addNewProxy() {
Ui::show(_controller->addNewItemBox(), LayerOption::KeepOther);
}
@ -335,17 +387,44 @@ void ProxiesBox::applyView(View &&view) {
const auto wrap = _wrap
? _wrap.data()
: _initialWrap.data();
const auto [i, ok] = _rows.emplace(id, wrap->insert(
const auto [i, ok] = _rows.emplace(id, nullptr);
i->second.reset(wrap->insert(
0,
object_ptr<ProxyRow>(
wrap,
std::move(view))));
setupButtons(id, i->second);
setupButtons(id, i->second.get());
_noRows.reset();
} else if (view.host.isEmpty()) {
_rows.erase(i);
} else {
i->second->updateFields(std::move(view));
}
}
void ProxiesBox::createNoRowsLabel() {
_noRows.reset(_wrap->add(
object_ptr<Ui::FixedHeightWidget>(
_wrap,
rowHeight()),
st::proxyEmptyListPadding));
_noRows->resize(
(st::boxWideWidth
- st::proxyEmptyListPadding.left()
- st::proxyEmptyListPadding.right()),
_noRows->height());
const auto label = Ui::CreateChild<Ui::FlatLabel>(
_noRows.get(),
lang(lng_proxy_description),
Ui::FlatLabel::InitType::Simple,
st::proxyEmptyListLabel);
_noRows->widthValue(
) | rpl::start_with_next([=](int width) {
label->resizeToWidth(width);
label->moveToLeft(0, 0);
});
}
void ProxiesBox::setupButtons(int id, not_null<ProxyRow*> button) {
button->deleteClicks(
) | rpl::start_with_next([=] {
@ -372,11 +451,233 @@ ProxyBox::ProxyBox(
QWidget*,
const ProxyData &data,
base::lambda<void(ProxyData)> callback)
: _callback(std::move(callback)) {
: _callback(std::move(callback))
, _content(this) {
setupControls(data);
}
void ProxyBox::prepare() {
setTitle(langFactory(lng_proxy_edit));
refreshButtons();
_content->heightValue(
) | rpl::start_with_next([=](int height) {
setDimensions(st::boxWidth, height);
}, _content->lifetime());
}
void ProxyBox::refreshButtons() {
clearButtons();
addButton(langFactory(lng_settings_save), [=] { save(); });
addButton(langFactory(lng_cancel), [=] { closeBox(); });
const auto type = _type->value();
if (type == Type::Socks5 || type == Type::Mtproto) {
addLeftButton(langFactory(lng_proxy_share), [=] { share(); });
}
}
void ProxyBox::save() {
if (const auto data = collectData()) {
_callback(data);
closeBox();
}
}
void ProxyBox::share() {
if (const auto data = collectData()) {
if (data.type == Type::Http) {
return;
}
const auto link = qsl("https://t.me/")
+ (data.type == Type::Socks5 ? "socks" : "proxy")
+ "?server=" + data.host + "&port=" + QString::number(data.port)
+ ((data.type == Type::Socks5 && !data.user.isEmpty())
? "&user=" + qthelp::url_encode(data.user) : "")
+ ((data.type == Type::Socks5 && !data.password.isEmpty())
? "&pass=" + qthelp::url_encode(data.password) : "")
+ ((data.type == Type::Mtproto && !data.password.isEmpty())
? "&secret=" + data.password : "");
Application::clipboard()->setText(link);
Ui::Toast::Show(lang(lng_username_copied));
}
}
ProxyData ProxyBox::collectData() {
auto result = ProxyData();
result.type = _type->value();
result.host = _host->getLastText().trimmed();
result.port = _port->getLastText().trimmed().toInt();
result.user = (result.type == Type::Mtproto)
? QString()
: _user->getLastText();
result.password = (result.type == Type::Mtproto)
? _secret->getLastText()
: _password->getLastText();
if (result.host.isEmpty()) {
_host->showError();
} else if (!result.port) {
_port->showError();
} else if ((result.type == Type::Http || result.type == Type::Socks5)
&& !result.password.isEmpty() && result.user.isEmpty()) {
_user->showError();
} else if (result.type == Type::Mtproto
&& result.password.size() != 32) {
_secret->showError();
} else if (!result) {
_host->showError();
} else {
return result;
}
return ProxyData();
}
void ProxyBox::setupTypes() {
const auto types = std::map<Type, QString>{
{ Type::Http, "HTTP" },
{ Type::Socks5, "SOCKS5" },
{ Type::Mtproto, "MTPROTO" },
};
for (const auto [type, label] : types) {
_content->add(
object_ptr<Ui::Radioenum<Type>>(
_content,
_type,
type,
label),
st::proxyEditTypePadding);
}
}
void ProxyBox::setupSocketAddress(const ProxyData &data) {
addLabel(_content, lang(lng_proxy_address_label));
const auto address = _content->add(
object_ptr<Ui::FixedHeightWidget>(
_content,
st::connectionHostInputField.heightMin),
st::proxyEditInputPadding);
_host = Ui::CreateChild<Ui::InputField>(
address,
st::connectionHostInputField,
langFactory(lng_connection_host_ph),
data.host);
_port = Ui::CreateChild<Ui::PortInput>(
address,
st::connectionPortInputField,
langFactory(lng_connection_port_ph),
data.port ? QString::number(data.port) : QString());
address->widthValue(
) | rpl::start_with_next([=](int width) {
_port->moveToRight(0, 0);
_host->resize(
width - _port->width() - st::proxyEditSkip,
_host->height());
_host->moveToLeft(0, 0);
}, address->lifetime());
}
void ProxyBox::setupCredentials(const ProxyData &data) {
_credentials = _content->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_content,
object_ptr<Ui::VerticalLayout>(_content)));
const auto credentials = _credentials->entity();
addLabel(credentials, lang(lng_proxy_credentials_optional));
_user = credentials->add(
object_ptr<Ui::InputField>(
credentials,
st::connectionUserInputField,
langFactory(lng_connection_user_ph),
data.user),
st::proxyEditInputPadding);
auto passwordWrap = object_ptr<Ui::RpWidget>(credentials);
_password = Ui::CreateChild<Ui::PasswordInput>(
passwordWrap.data(),
st::connectionPasswordInputField,
langFactory(lng_connection_password_ph),
(data.type == Type::Mtproto) ? QString() : data.password);
_password->move(0, 0);
_password->heightValue(
) | rpl::start_with_next([=, wrap = passwordWrap.data()](int height) {
wrap->resize(wrap->width(), height);
}, _password->lifetime());
passwordWrap->widthValue(
) | rpl::start_with_next([=](int width) {
_password->resize(width, _password->height());
}, _password->lifetime());
credentials->add(std::move(passwordWrap), st::proxyEditInputPadding);
}
void ProxyBox::setupMtprotoCredentials(const ProxyData &data) {
_mtprotoCredentials = _content->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_content,
object_ptr<Ui::VerticalLayout>(_content)));
const auto mtproto = _mtprotoCredentials->entity();
addLabel(mtproto, lang(lng_proxy_credentials));
auto secretWrap = object_ptr<Ui::RpWidget>(mtproto);
_secret = Ui::CreateChild<Ui::HexInput>(
secretWrap.data(),
st::connectionUserInputField,
langFactory(lng_connection_proxy_secret_ph),
(data.type == Type::Mtproto) ? data.password : QString());
_secret->setMaxLength(32);
_secret->move(0, 0);
_secret->heightValue(
) | rpl::start_with_next([=, wrap = secretWrap.data()](int height) {
wrap->resize(wrap->width(), height);
}, _secret->lifetime());
secretWrap->widthValue(
) | rpl::start_with_next([=](int width) {
_secret->resize(width, _secret->height());
}, _secret->lifetime());
mtproto->add(std::move(secretWrap), st::proxyEditInputPadding);
}
void ProxyBox::setupControls(const ProxyData &data) {
_type = std::make_shared<Ui::RadioenumGroup<Type>>(
(data.type == Type::None
? Type::Socks5
: data.type));
_content.create(this);
_content->resizeToWidth(st::boxWidth);
_content->moveToLeft(0, 0);
setupTypes();
setupSocketAddress(data);
setupCredentials(data);
setupMtprotoCredentials(data);
_content->resizeToWidth(st::boxWidth);
const auto handleType = [=](Type type) {
_credentials->toggle(
type == Type::Http || type == Type::Socks5,
anim::type::instant);
_mtprotoCredentials->toggle(
type == Type::Mtproto,
anim::type::instant);
};
_type->setChangedCallback([=](Type type) {
handleType(type);
refreshButtons();
});
handleType(_type->value());
}
void ProxyBox::addLabel(
not_null<Ui::VerticalLayout*> parent,
const QString &text) const {
parent->add(
object_ptr<Ui::FlatLabel>(
parent,
text,
Ui::FlatLabel::InitType::Simple,
st::proxyEditTitle),
st::proxyEditTitlePadding);
}
} // namespace
@ -844,22 +1145,46 @@ object_ptr<BoxContent> ProxiesBoxController::editItemBox(int id) {
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);
}
replaceItemWith(i, j);
} else {
i->data = result;
if (i->deleted) {
restoreItem(i->id);
} else {
updateView(*i);
}
replaceItemValue(i, result);
}
});
}
void ProxiesBoxController::replaceItemWith(
std::vector<Item>::iterator which,
std::vector<Item>::iterator with) {
auto &proxies = Global::RefProxiesList();
proxies.erase(ranges::remove(proxies, which->data), end(proxies));
_views.fire({ which->id });
_list.erase(which);
if (with->deleted) {
restoreItem(with->id);
}
applyItem(with->id);
saveDelayed();
}
void ProxiesBoxController::replaceItemValue(
std::vector<Item>::iterator which,
const ProxyData &proxy) {
if (which->deleted) {
restoreItem(which->id);
}
auto &proxies = Global::RefProxiesList();
const auto i = ranges::find(proxies, which->data);
Assert(i != end(proxies));
*i = proxy;
which->data = proxy;
applyItem(which->id);
saveDelayed();
}
object_ptr<BoxContent> ProxiesBoxController::addNewItemBox() {
return Box<ProxyBox>(ProxyData(), [=](const ProxyData &result) {
auto j = ranges::find(
@ -870,13 +1195,21 @@ object_ptr<BoxContent> ProxiesBoxController::addNewItemBox() {
if (j->deleted) {
restoreItem(j->id);
}
applyItem(j->id);
} else {
_list.push_back({ ++_idCounter, result });
updateView(_list.back());
addNewItem(result);
}
});
}
void ProxiesBoxController::addNewItem(const ProxyData &proxy) {
auto &proxies = Global::RefProxiesList();
proxies.push_back(proxy);
_list.push_back({ ++_idCounter, proxy });
applyItem(_list.back().id);
}
bool ProxiesBoxController::setProxyEnabled(bool enabled) {
if (enabled) {
if (Global::ProxiesList().empty()) {
@ -894,6 +1227,14 @@ bool ProxiesBoxController::setProxyEnabled(bool enabled) {
return true;
}
void ProxiesBoxController::setTryIPv6(bool enabled) {
if (Global::TryIPv6() == enabled) {
return;
}
Global::SetTryIPv6(enabled);
applyChanges();
}
void ProxiesBoxController::applyChanges() {
Sandbox::refreshGlobalProxy();
Global::RefConnectionTypeChanged().notify();

View File

@ -124,6 +124,7 @@ public:
object_ptr<BoxContent> editItemBox(int id);
object_ptr<BoxContent> addNewItemBox();
bool setProxyEnabled(bool enabled);
void setTryIPv6(bool enabled);
rpl::producer<ItemView> views() const;
@ -143,6 +144,14 @@ private:
void applyChanges();
void saveDelayed();
void replaceItemWith(
std::vector<Item>::iterator which,
std::vector<Item>::iterator with);
void replaceItemValue(
std::vector<Item>::iterator which,
const ProxyData &proxy);
void addNewItem(const ProxyData &proxy);
int _idCounter = 0;
int _selected = -1;
std::vector<Item> _list;

View File

@ -3944,6 +3944,33 @@ void PortInput::correctValue(
setCorrectedText(now, nowCursor, newText, newPos);
}
HexInput::HexInput(QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory, const QString &val) : MaskedInputField(parent, st, std::move(placeholderFactory), val) {
if (!QRegularExpression("^[a-fA-F0-9]+$").match(val).hasMatch()) {
setText(QString());
}
}
void HexInput::correctValue(
const QString &was,
int wasCursor,
QString &now,
int &nowCursor) {
QString newText;
newText.reserve(now.size());
auto newPos = nowCursor;
for (auto i = 0, l = now.size(); i < l; ++i) {
const auto ch = now[i];
if ((ch >= '0' && ch <= '9')
|| (ch >= 'a' && ch <= 'f')
|| (ch >= 'A' && ch <= 'F')) {
newText.append(ch);
} else if (i < nowCursor) {
--newPos;
}
}
setCorrectedText(now, nowCursor, newText, newPos);
}
UsernameInput::UsernameInput(QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory, const QString &val, bool isLink) : MaskedInputField(parent, st, std::move(placeholderFactory), val) {
setLinkPlaceholder(isLink ? Messenger::Instance().createInternalLink(QString()) : QString());
}

View File

@ -906,6 +906,19 @@ protected:
};
class HexInput : public MaskedInputField {
public:
HexInput(QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory, const QString &val);
protected:
void correctValue(
const QString &was,
int wasCursor,
QString &now,
int &nowCursor) override;
};
class UsernameInput : public MaskedInputField {
public:
UsernameInput(QWidget *parent, const style::InputField &st, base::lambda<QString()> placeholderFactory, const QString &val, bool isLink);

View File

@ -164,8 +164,8 @@ void VerticalLayout::removeChild(RpWidget *child) {
auto prev = it - 1;
return prev->widget->bottomNoMargins() + prev->margin.bottom();
}() - margins.top();
for (auto next = it + 1; it != end; ++it) {
auto &row = *it;
for (auto next = it + 1; next != end; ++next) {
auto &row = *next;
auto margin = row.margin;
auto widget = row.widget.data();
widget->moveToLeft(
@ -175,6 +175,7 @@ void VerticalLayout::removeChild(RpWidget *child) {
+ widget->heightNoMargins()
+ margin.bottom();
}
it->widget = nullptr;
_rows.erase(it);
resize(width(), margins.top() + top + margins.bottom());