2
0
mirror of https://github.com/kotatogram/kotatogram-desktop synced 2025-09-03 08:05:12 +00:00

Improve edit privacy box design.

Also move calls peer-to-peer settings to EditPrivacyBox.
This commit is contained in:
John Preston
2018-09-19 12:06:21 +03:00
parent 3ba2a7931e
commit 9388e154cf
9 changed files with 316 additions and 409 deletions

View File

@@ -7,18 +7,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/edit_privacy_box.h"
#include "styles/style_boxes.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "history/history.h"
#include "boxes/peer_list_controllers.h"
#include "info/profile/info_profile_button.h"
#include "settings/settings_common.h"
#include "calls/calls_instance.h"
#include "base/binary_guard.h"
#include "lang/lang_keys.h"
#include "apiwrap.h"
#include "auth_session.h"
#include "styles/style_settings.h"
#include "styles/style_boxes.h"
namespace {
@@ -82,111 +86,32 @@ std::unique_ptr<PrivacyExceptionsBoxController::Row> PrivacyExceptionsBoxControl
EditPrivacyBox::EditPrivacyBox(
QWidget*,
std::unique_ptr<Controller> controller,
rpl::producer<Value> preloaded)
const Value &value)
: _controller(std::move(controller))
, _loading(this, lang(lng_contacts_loading), Ui::FlatLabel::InitType::Simple, st::membersAbout) {
std::move(
preloaded
) | rpl::take(
1
) | rpl::start_with_next([=](Value &&data) {
dataReady(std::move(data));
}, lifetime());
, _value(value) {
}
void EditPrivacyBox::prepare() {
_controller->setView(this);
setTitle([this] { return _controller->title(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
setTitle([=] { return _controller->title(); });
if (_loading) {
_prepared = true;
} else {
createWidgets();
}
setDimensions(st::boxWideWidth, countDefaultHeight(st::boxWideWidth));
setupContent();
}
int EditPrivacyBox::resizeGetHeight(int newWidth) {
auto top = 0;
auto layoutRow = [&](auto &widget, style::margins padding) {
if (!widget) return;
widget->resizeToNaturalWidth(newWidth - padding.left() - padding.right());
widget->moveToLeft(padding.left(), top + padding.top());
top = widget->bottomNoMargins() + padding.bottom();
};
layoutRow(_description, st::editPrivacyPadding);
layoutRow(_everyone, st::editPrivacyOptionMargin);
layoutRow(_contacts, st::editPrivacyOptionMargin);
layoutRow(_nobody, st::editPrivacyOptionMargin);
layoutRow(_warning, st::editPrivacyWarningPadding);
layoutRow(_exceptionsTitle, st::editPrivacyTitlePadding);
auto linksTop = top;
layoutRow(_alwaysLink, st::editPrivacyPadding);
layoutRow(_neverLink, st::editPrivacyPadding);
auto linksHeight = top - linksTop;
layoutRow(_exceptionsDescription, st::editPrivacyPadding);
// Add full width of both links in any case
auto linkMargins = exceptionLinkMargins();
top -= linksHeight;
top += linkMargins.top() + st::boxLinkButton.font->height + linkMargins.bottom();
top += linkMargins.top() + st::boxLinkButton.font->height + linkMargins.bottom();
return top;
}
void EditPrivacyBox::resizeEvent(QResizeEvent *e) {
if (_loading) {
_loading->moveToLeft((width() - _loading->width()) / 2, height() / 3);
}
}
int EditPrivacyBox::countDefaultHeight(int newWidth) {
auto height = 0;
auto optionHeight = [this](Option option) {
if (!_controller->hasOption(option)) {
return 0;
}
return st::editPrivacyOptionMargin.top() + st::defaultCheck.diameter + st::editPrivacyOptionMargin.bottom();
};
auto labelHeight = [newWidth](const QString &text, const style::FlatLabel &st, style::margins padding) {
if (text.isEmpty()) {
return 0;
}
auto fake = object_ptr<Ui::FlatLabel>(nullptr, text, Ui::FlatLabel::InitType::Simple, st);
fake->resizeToNaturalWidth(newWidth - padding.left() - padding.right());
return padding.top() + fake->heightNoMargins() + padding.bottom();
};
auto linkHeight = [this]() {
auto linkMargins = exceptionLinkMargins();
return linkMargins.top() + st::boxLinkButton.font->height + linkMargins.bottom();
};
height += labelHeight(_controller->description(), st::editPrivacyLabel, st::editPrivacyPadding);
height += optionHeight(Option::Everyone);
height += optionHeight(Option::Contacts);
height += optionHeight(Option::Nobody);
height += labelHeight(_controller->warning(), st::editPrivacyLabel, st::editPrivacyWarningPadding);
height += labelHeight(lang(lng_edit_privacy_exceptions), st::editPrivacyTitle, st::editPrivacyTitlePadding);
height += linkHeight();
height += linkHeight();
height += labelHeight(_controller->exceptionsDescription(), st::editPrivacyLabel, st::editPrivacyPadding);
return height;
}
void EditPrivacyBox::editExceptionUsers(Exception exception) {
auto controller = std::make_unique<PrivacyExceptionsBoxController>(crl::guard(this, [this, exception] {
return _controller->exceptionBoxTitle(exception);
}), exceptionUsers(exception));
auto initBox = [this, exception, controller = controller.get()](not_null<PeerListBox*> box) {
box->addButton(langFactory(lng_settings_save), crl::guard(this, [this, box, exception, controller] {
void EditPrivacyBox::editExceptionUsers(
Exception exception,
Fn<void()> done) {
auto controller = std::make_unique<PrivacyExceptionsBoxController>(
crl::guard(this, [=] {
return _controller->exceptionBoxTitle(exception);
}),
exceptionUsers(exception));
auto initBox = [=, controller = controller.get()](
not_null<PeerListBox*> box) {
box->addButton(langFactory(lng_settings_save), crl::guard(this, [=] {
exceptionUsers(exception) = controller->getResult();
exceptionLink(exception)->entity()->setText(exceptionLinkText(exception));
auto removeFrom = ([exception] {
const auto removeFrom = ([=] {
switch (exception) {
case Exception::Always: return Exception::Never;
case Exception::Never: return Exception::Always;
@@ -194,30 +119,20 @@ void EditPrivacyBox::editExceptionUsers(Exception exception) {
Unexpected("Invalid exception value.");
})();
auto &removeFromUsers = exceptionUsers(removeFrom);
auto removedSome = false;
for (auto user : exceptionUsers(exception)) {
auto removedStart = std::remove(removeFromUsers.begin(), removeFromUsers.end(), user);
if (removedStart != removeFromUsers.end()) {
removeFromUsers.erase(removedStart, removeFromUsers.end());
removedSome = true;
}
}
if (removedSome) {
exceptionLink(removeFrom)->entity()->setText(exceptionLinkText(removeFrom));
for (const auto user : exceptionUsers(exception)) {
const auto from = ranges::remove(removeFromUsers, user);
removeFromUsers.erase(from, end(removeFromUsers));
}
done();
box->closeBox();
}));
box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); });
box->addButton(langFactory(lng_cancel), [=] { box->closeBox(); });
};
Ui::show(
Box<PeerListBox>(std::move(controller), std::move(initBox)),
LayerOption::KeepOther);
}
QString EditPrivacyBox::exceptionLinkText(Exception exception) {
return _controller->exceptionLinkText(exception, exceptionUsers(exception).size());
}
QVector<MTPInputPrivacyRule> EditPrivacyBox::collectResult() {
auto collectInputUsers = [](auto &users) {
auto result = QVector<MTPInputUser>();
@@ -237,19 +152,18 @@ QVector<MTPInputPrivacyRule> EditPrivacyBox::collectResult() {
if (showExceptionLink(Exception::Never) && !_value.never.empty()) {
result.push_back(MTP_inputPrivacyValueDisallowUsers(MTP_vector<MTPInputUser>(collectInputUsers(_value.never))));
}
switch (_value.option) {
case Option::Everyone: result.push_back(MTP_inputPrivacyValueAllowAll()); break;
case Option::Contacts: result.push_back(MTP_inputPrivacyValueAllowContacts()); break;
case Option::Nobody: result.push_back(MTP_inputPrivacyValueDisallowAll()); break;
}
result.push_back([&] {
switch (_value.option) {
case Option::Everyone: return MTP_inputPrivacyValueAllowAll();
case Option::Contacts: return MTP_inputPrivacyValueAllowContacts();
case Option::Nobody: return MTP_inputPrivacyValueDisallowAll();
}
Unexpected("Option value in EditPrivacyBox::collectResult.");
}());
return result;
}
style::margins EditPrivacyBox::exceptionLinkMargins() const {
return st::editPrivacyLinkMargin;
}
std::vector<not_null<UserData*>> &EditPrivacyBox::exceptionUsers(Exception exception) {
switch (exception) {
case Exception::Always: return _value.always;
@@ -258,154 +172,165 @@ std::vector<not_null<UserData*>> &EditPrivacyBox::exceptionUsers(Exception excep
Unexpected("Invalid exception value.");
}
object_ptr<Ui::SlideWrap<Ui::LinkButton>> &EditPrivacyBox::exceptionLink(Exception exception) {
switch (exception) {
case Exception::Always: return _alwaysLink;
case Exception::Never: return _neverLink;
}
Unexpected("Invalid exception value.");
}
bool EditPrivacyBox::showExceptionLink(Exception exception) const {
switch (exception) {
case Exception::Always: return (_value.option == Option::Contacts) || (_value.option == Option::Nobody);
case Exception::Never: return (_value.option == Option::Everyone) || (_value.option == Option::Contacts);
case Exception::Always:
return (_value.option == Option::Contacts)
|| (_value.option == Option::Nobody);
case Exception::Never:
return (_value.option == Option::Everyone)
|| (_value.option == Option::Contacts);
}
Unexpected("Invalid exception value.");
}
void EditPrivacyBox::createWidgets() {
_loading.destroy();
_optionGroup = std::make_shared<Ui::RadioenumGroup<Option>>(_value.option);
auto createOption = [this](object_ptr<Ui::Radioenum<Option>> &widget, Option option, const QString &label) {
if (_controller->hasOption(option) || (_value.option == option)) {
widget.create(this, _optionGroup, option, label, st::defaultBoxCheckbox);
Ui::Radioenum<EditPrivacyBox::Option> *EditPrivacyBox::AddOption(
not_null<Ui::VerticalLayout*> container,
const std::shared_ptr<Ui::RadioenumGroup<Option>> &group,
Option option) {
const auto label = [&] {
switch (option) {
case Option::Everyone: return lng_edit_privacy_everyone;
case Option::Contacts: return lng_edit_privacy_contacts;
case Option::Nobody: return lng_edit_privacy_nobody;
}
Unexpected("Option value in EditPrivacyBox::AddOption.");
}();
return container->add(
object_ptr<Ui::Radioenum<Option>>(
container,
group,
option,
lang(label),
st::settingsSendType),
st::settingsSendTypePadding);
}
Ui::FlatLabel *EditPrivacyBox::AddLabel(
not_null<Ui::VerticalLayout*> container,
rpl::producer<QString> text) {
const auto wrap = container->add(
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
container,
object_ptr<Ui::FlatLabel>(
container,
rpl::duplicate(text),
st::boxDividerLabel),
st::settingsPrivacyEditLabelPadding));
wrap->hide(anim::type::instant);
wrap->toggleOn(std::move(
text
) | rpl::map([](const QString &text) {
return !text.isEmpty();
}));
return wrap->entity();
}
void EditPrivacyBox::setupContent() {
using namespace Settings;
auto wrap = object_ptr<Ui::VerticalLayout>(this);
const auto content = wrap.data();
setInnerWidget(object_ptr<Ui::OverrideMargins>(
this,
std::move(wrap)));
const auto group = std::make_shared<Ui::RadioenumGroup<Option>>(
_value.option);
const auto toggle = Ui::AttachAsChild(content, rpl::event_stream<>());
group->setChangedCallback([=](Option value) {
_value.option = value;
toggle->fire({});
});
const auto addOption = [&](Option option) {
return (_controller->hasOption(option) || (_value.option == option))
? AddOption(content, group, option)
: nullptr;
};
auto createLabel = [this](object_ptr<Ui::FlatLabel> &widget, const QString &text, const style::FlatLabel &st) {
if (text.isEmpty()) {
return;
}
widget.create(this, text, Ui::FlatLabel::InitType::Simple, st);
};
auto createExceptionLink = [this](Exception exception) {
exceptionLink(exception).create(this, object_ptr<Ui::LinkButton>(this, exceptionLinkText(exception)), exceptionLinkMargins());
exceptionLink(exception)->heightValue(
) | rpl::start_with_next([this] {
resizeToWidth(width());
}, lifetime());
exceptionLink(exception)->entity()->setClickedCallback([this, exception] { editExceptionUsers(exception); });
const auto addExceptionLink = [=](Exception exception) {
const auto update = Ui::AttachAsChild(
content,
rpl::event_stream<>());
auto label = update->events_starting_with(
rpl::empty_value()
) | rpl::map([=] {
return exceptionUsers(exception).size();
}) | rpl::map([](int count) {
return count
? lng_edit_privacy_exceptions_count(lt_count, count)
: lang(lng_edit_privacy_exceptions_add);
});
auto text = _controller->exceptionButtonTextKey(exception);
const auto button = content->add(
object_ptr<Ui::SlideWrap<Button>>(
content,
object_ptr<Button>(
content,
Lang::Viewer(text),
st::settingsButton)));
CreateRightLabel(
button->entity(),
std::move(label),
st::settingsButton,
text);
button->toggleOn(toggle->events_starting_with(
rpl::empty_value()
) | rpl::map([=] {
return showExceptionLink(exception);
}))->entity()->addClickHandler([=] {
editExceptionUsers(exception, [=] { update->fire({}); });
});
return button;
};
createLabel(_description, _controller->description(), st::editPrivacyLabel);
createOption(_everyone, Option::Everyone, lang(lng_edit_privacy_everyone));
createOption(_contacts, Option::Contacts, lang(lng_edit_privacy_contacts));
createOption(_nobody, Option::Nobody, lang(lng_edit_privacy_nobody));
createLabel(_warning, _controller->warning(), st::editPrivacyLabel);
createLabel(_exceptionsTitle, lang(lng_edit_privacy_exceptions), st::editPrivacyTitle);
createExceptionLink(Exception::Always);
createExceptionLink(Exception::Never);
createLabel(_exceptionsDescription, _controller->exceptionsDescription(), st::editPrivacyLabel);
AddSubsectionTitle(content, _controller->optionsTitleKey());
addOption(Option::Everyone);
addOption(Option::Contacts);
addOption(Option::Nobody);
AddLabel(content, _controller->warning());
AddSkip(content);
clearButtons();
addButton(langFactory(lng_settings_save), [this] {
auto someAreDisallowed = (_value.option != Option::Everyone) || !_value.never.empty();
_controller->confirmSave(someAreDisallowed, crl::guard(this, [this] {
AddDivider(content);
AddSkip(content);
AddSubsectionTitle(content, lng_edit_privacy_exceptions);
const auto always = addExceptionLink(Exception::Always);
const auto never = addExceptionLink(Exception::Never);
AddLabel(content, _controller->exceptionsDescription());
AddSkip(content);
const auto saveAdditional = _controller->setupAdditional(content);
addButton(langFactory(lng_settings_save), [=] {
const auto someAreDisallowed = (_value.option != Option::Everyone)
|| !_value.never.empty();
_controller->confirmSave(someAreDisallowed, crl::guard(this, [=] {
Auth().api().savePrivacy(
_controller->apiKey(),
collectResult());
if (saveAdditional) {
saveAdditional();
}
closeBox();
}));
});
addButton(langFactory(lng_cancel), [this] { closeBox(); });
_optionGroup->setChangedCallback([this](Option value) {
_value.option = value;
_alwaysLink->toggle(
showExceptionLink(Exception::Always),
anim::type::normal);
_neverLink->toggle(
showExceptionLink(Exception::Never),
anim::type::normal);
});
const auto linkHeight = st::settingsButton.padding.top()
+ st::settingsButton.height
+ st::settingsButton.padding.bottom();
showChildren();
_alwaysLink->toggle(
showExceptionLink(Exception::Always),
anim::type::instant);
_neverLink->toggle(
showExceptionLink(Exception::Never),
anim::type::instant);
widthValue(
) | rpl::start_with_next([=](int width) {
content->resizeToWidth(width);
}, content->lifetime());
setDimensions(st::boxWideWidth, resizeGetHeight(st::boxWideWidth));
}
void EditPrivacyBox::dataReady(Value &&value) {
_value = std::move(value);
_loading.destroy();
if (_prepared) {
createWidgets();
}
}
void EditCallsPeerToPeer::prepare() {
setTitle(langFactory(lng_settings_peer_to_peer));
addButton(langFactory(lng_box_ok), [=] { closeBox(); });
const auto options = {
PeerToPeer::Everyone,
PeerToPeer::Contacts,
PeerToPeer::Nobody
};
const auto value = Auth().settings().callsPeerToPeer();
const auto adjusted = [&] {
if (value == PeerToPeer::DefaultContacts) {
return PeerToPeer::Contacts;
} else if (value == PeerToPeer::DefaultEveryone) {
return PeerToPeer::Everyone;
}
return value;
}();
const auto label = [](PeerToPeer value) {
switch (value) {
case PeerToPeer::Everyone: return lang(lng_edit_privacy_everyone);
case PeerToPeer::Contacts: return lang(lng_edit_privacy_contacts);
case PeerToPeer::Nobody: return lang(lng_edit_privacy_nobody);
}
Unexpected("Adjusted Calls::PeerToPeer value.");
};
auto group = std::make_shared<Ui::RadioenumGroup<PeerToPeer>>(adjusted);
auto y = st::boxOptionListPadding.top() + st::langsButton.margin.top();
auto count = int(options.size());
_options.reserve(count);
for (const auto option : options) {
_options.emplace_back(
this,
group,
option,
label(option),
st::langsButton);
_options.back()->moveToLeft(
st::boxPadding.left() + st::boxOptionListPadding.left(),
y);
y += _options.back()->heightNoMargins() + st::boxOptionListSkip;
}
group->setChangedCallback([=](PeerToPeer value) { chosen(value); });
setDimensions(
st::langsWidth,
(st::boxOptionListPadding.top()
+ count * _options.back()->heightNoMargins()
+ (count - 1) * st::boxOptionListSkip
+ st::boxOptionListPadding.bottom()
+ st::boxPadding.bottom()));
}
void EditCallsPeerToPeer::chosen(PeerToPeer value) {
Auth().settings().setCallsPeerToPeer(value);
Auth().saveSettingsDelayed();
closeBox();
content->heightValue(
) | rpl::map([=](int height) {
return height - always->height() - never->height() + 2 * linkHeight;
}) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](int height) {
setDimensions(st::boxWideWidth, height);
}, content->lifetime());
}