mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-30 07:18:28 +00:00
Show current privacy values in settings section.
This commit is contained in:
parent
165511fb14
commit
12ebae01b0
@ -147,6 +147,16 @@ ApiWrap::MessageToSend::MessageToSend(not_null<History*> history)
|
|||||||
: history(history) {
|
: history(history) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MTPInputPrivacyKey ApiWrap::Privacy::Input(Key key) {
|
||||||
|
switch (key) {
|
||||||
|
case Privacy::Key::Calls: return MTP_inputPrivacyKeyPhoneCall();
|
||||||
|
case Privacy::Key::Invites: return MTP_inputPrivacyKeyChatInvite();
|
||||||
|
case Privacy::Key::LastSeen:
|
||||||
|
return MTP_inputPrivacyKeyStatusTimestamp();
|
||||||
|
}
|
||||||
|
Unexpected("Key in ApiWrap::Privacy::Input.");
|
||||||
|
}
|
||||||
|
|
||||||
ApiWrap::ApiWrap(not_null<AuthSession*> session)
|
ApiWrap::ApiWrap(not_null<AuthSession*> session)
|
||||||
: _session(session)
|
: _session(session)
|
||||||
, _messageDataResolveDelayed([=] { resolveMessageDatas(); })
|
, _messageDataResolveDelayed([=] { resolveMessageDatas(); })
|
||||||
@ -1880,112 +1890,139 @@ void ApiWrap::saveDraftToCloudDelayed(not_null<History*> history) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ApiWrap::savePrivacy(const MTPInputPrivacyKey &key, QVector<MTPInputPrivacyRule> &&rules) {
|
void ApiWrap::savePrivacy(const MTPInputPrivacyKey &key, QVector<MTPInputPrivacyRule> &&rules) {
|
||||||
auto keyTypeId = key.type();
|
const auto keyTypeId = key.type();
|
||||||
auto it = _privacySaveRequests.find(keyTypeId);
|
const auto it = _privacySaveRequests.find(keyTypeId);
|
||||||
if (it != _privacySaveRequests.cend()) {
|
if (it != _privacySaveRequests.cend()) {
|
||||||
request(it->second).cancel();
|
request(it->second).cancel();
|
||||||
_privacySaveRequests.erase(it);
|
_privacySaveRequests.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto requestId = request(MTPaccount_SetPrivacy(key, MTP_vector<MTPInputPrivacyRule>(std::move(rules)))).done([this, keyTypeId](const MTPaccount_PrivacyRules &result) {
|
const auto requestId = request(MTPaccount_SetPrivacy(
|
||||||
|
key,
|
||||||
|
MTP_vector<MTPInputPrivacyRule>(std::move(rules))
|
||||||
|
)).done([=](const MTPaccount_PrivacyRules &result) {
|
||||||
Expects(result.type() == mtpc_account_privacyRules);
|
Expects(result.type() == mtpc_account_privacyRules);
|
||||||
|
|
||||||
auto &rules = result.c_account_privacyRules();
|
auto &rules = result.c_account_privacyRules();
|
||||||
App::feedUsers(rules.vusers);
|
App::feedUsers(rules.vusers);
|
||||||
_privacySaveRequests.remove(keyTypeId);
|
_privacySaveRequests.remove(keyTypeId);
|
||||||
handlePrivacyChange(keyTypeId, rules.vrules);
|
handlePrivacyChange(keyTypeId, rules.vrules);
|
||||||
}).fail([this, keyTypeId](const RPCError &error) {
|
}).fail([=](const RPCError &error) {
|
||||||
_privacySaveRequests.remove(keyTypeId);
|
_privacySaveRequests.remove(keyTypeId);
|
||||||
}).send();
|
}).send();
|
||||||
|
|
||||||
_privacySaveRequests.emplace(keyTypeId, requestId);
|
_privacySaveRequests.emplace(keyTypeId, requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApiWrap::handlePrivacyChange(mtpTypeId keyTypeId, const MTPVector<MTPPrivacyRule> &rules) {
|
void ApiWrap::handlePrivacyChange(
|
||||||
if (keyTypeId == mtpc_privacyKeyStatusTimestamp) {
|
mtpTypeId keyTypeId,
|
||||||
enum class Rule {
|
const MTPVector<MTPPrivacyRule> &rules) {
|
||||||
Unknown,
|
using Key = Privacy::Key;
|
||||||
Allow,
|
const auto key = [&]() -> base::optional<Key> {
|
||||||
Disallow,
|
switch (keyTypeId) {
|
||||||
};
|
case mtpc_privacyKeyStatusTimestamp:
|
||||||
auto userRules = QMap<UserId, Rule>();
|
case mtpc_inputPrivacyKeyStatusTimestamp: return Key::LastSeen;
|
||||||
auto contactsRule = Rule::Unknown;
|
case mtpc_privacyKeyChatInvite:
|
||||||
auto everyoneRule = Rule::Unknown;
|
case mtpc_inputPrivacyKeyChatInvite: return Key::Invites;
|
||||||
for (auto &rule : rules.v) {
|
case mtpc_privacyKeyPhoneCall:
|
||||||
auto type = rule.type();
|
case mtpc_inputPrivacyKeyPhoneCall: return Key::Calls;
|
||||||
if (type != mtpc_privacyValueAllowAll && type != mtpc_privacyValueDisallowAll && contactsRule != Rule::Unknown) {
|
|
||||||
// This is simplified: we ignore per-user rules that come after a contacts rule.
|
|
||||||
// But none of the official apps provide such complicated rule sets, so its fine.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case mtpc_privacyValueAllowAll: everyoneRule = Rule::Allow; break;
|
|
||||||
case mtpc_privacyValueDisallowAll: everyoneRule = Rule::Disallow; break;
|
|
||||||
case mtpc_privacyValueAllowContacts: contactsRule = Rule::Allow; break;
|
|
||||||
case mtpc_privacyValueDisallowContacts: contactsRule = Rule::Disallow; break;
|
|
||||||
case mtpc_privacyValueAllowUsers: {
|
|
||||||
for_const (auto &userId, rule.c_privacyValueAllowUsers().vusers.v) {
|
|
||||||
if (!userRules.contains(userId.v)) {
|
|
||||||
userRules.insert(userId.v, Rule::Allow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
case mtpc_privacyValueDisallowUsers: {
|
|
||||||
for_const (auto &userId, rule.c_privacyValueDisallowUsers().vusers.v) {
|
|
||||||
if (!userRules.contains(userId.v)) {
|
|
||||||
userRules.insert(userId.v, Rule::Disallow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
if (everyoneRule != Rule::Unknown) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return base::none;
|
||||||
auto now = unixtime();
|
}();
|
||||||
App::enumerateUsers([&](UserData *user) {
|
if (!key) {
|
||||||
if (user->isSelf() || user->loadedStatus != PeerData::FullLoaded) {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (user->onlineTill <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user->onlineTill + 3 * 86400 >= now) {
|
|
||||||
user->onlineTill = -2; // recently
|
|
||||||
} else if (user->onlineTill + 7 * 86400 >= now) {
|
|
||||||
user->onlineTill = -3; // last week
|
|
||||||
} else if (user->onlineTill + 30 * 86400 >= now) {
|
|
||||||
user->onlineTill = -4; // last month
|
|
||||||
} else {
|
|
||||||
user->onlineTill = 0;
|
|
||||||
}
|
|
||||||
Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserOnlineChanged);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (_contactsStatusesRequestId) {
|
|
||||||
request(_contactsStatusesRequestId).cancel();
|
|
||||||
}
|
|
||||||
_contactsStatusesRequestId = request(MTPcontacts_GetStatuses()).done([this](const MTPVector<MTPContactStatus> &result) {
|
|
||||||
_contactsStatusesRequestId = 0;
|
|
||||||
for_const (auto &item, result.v) {
|
|
||||||
Assert(item.type() == mtpc_contactStatus);
|
|
||||||
auto &data = item.c_contactStatus();
|
|
||||||
if (auto user = App::userLoaded(data.vuser_id.v)) {
|
|
||||||
auto oldOnlineTill = user->onlineTill;
|
|
||||||
auto newOnlineTill = onlineTillFromStatus(data.vstatus, oldOnlineTill);
|
|
||||||
if (oldOnlineTill != newOnlineTill) {
|
|
||||||
user->onlineTill = newOnlineTill;
|
|
||||||
Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserOnlineChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).fail([this](const RPCError &error) {
|
|
||||||
_contactsStatusesRequestId = 0;
|
|
||||||
}).send();
|
|
||||||
}
|
}
|
||||||
|
pushPrivacy(*key, rules.v);
|
||||||
|
if (*key == Key::LastSeen) {
|
||||||
|
updatePrivacyLastSeens(rules.v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiWrap::updatePrivacyLastSeens(const QVector<MTPPrivacyRule> &rules) {
|
||||||
|
enum class Rule {
|
||||||
|
Unknown,
|
||||||
|
Allow,
|
||||||
|
Disallow,
|
||||||
|
};
|
||||||
|
auto userRules = QMap<UserId, Rule>();
|
||||||
|
auto contactsRule = Rule::Unknown;
|
||||||
|
auto everyoneRule = Rule::Unknown;
|
||||||
|
for (auto &rule : rules) {
|
||||||
|
auto type = rule.type();
|
||||||
|
if (type != mtpc_privacyValueAllowAll
|
||||||
|
&& type != mtpc_privacyValueDisallowAll
|
||||||
|
&& contactsRule != Rule::Unknown) {
|
||||||
|
// This is simplified: we ignore per-user rules that come after a contacts rule.
|
||||||
|
// But none of the official apps provide such complicated rule sets, so its fine.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case mtpc_privacyValueAllowAll: everyoneRule = Rule::Allow; break;
|
||||||
|
case mtpc_privacyValueDisallowAll: everyoneRule = Rule::Disallow; break;
|
||||||
|
case mtpc_privacyValueAllowContacts: contactsRule = Rule::Allow; break;
|
||||||
|
case mtpc_privacyValueDisallowContacts: contactsRule = Rule::Disallow; break;
|
||||||
|
case mtpc_privacyValueAllowUsers: {
|
||||||
|
for_const (auto &userId, rule.c_privacyValueAllowUsers().vusers.v) {
|
||||||
|
if (!userRules.contains(userId.v)) {
|
||||||
|
userRules.insert(userId.v, Rule::Allow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case mtpc_privacyValueDisallowUsers: {
|
||||||
|
for_const (auto &userId, rule.c_privacyValueDisallowUsers().vusers.v) {
|
||||||
|
if (!userRules.contains(userId.v)) {
|
||||||
|
userRules.insert(userId.v, Rule::Disallow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
if (everyoneRule != Rule::Unknown) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto now = unixtime();
|
||||||
|
App::enumerateUsers([&](UserData *user) {
|
||||||
|
if (user->isSelf() || user->loadedStatus != PeerData::FullLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (user->onlineTill <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user->onlineTill + 3 * 86400 >= now) {
|
||||||
|
user->onlineTill = -2; // recently
|
||||||
|
} else if (user->onlineTill + 7 * 86400 >= now) {
|
||||||
|
user->onlineTill = -3; // last week
|
||||||
|
} else if (user->onlineTill + 30 * 86400 >= now) {
|
||||||
|
user->onlineTill = -4; // last month
|
||||||
|
} else {
|
||||||
|
user->onlineTill = 0;
|
||||||
|
}
|
||||||
|
Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserOnlineChanged);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (_contactsStatusesRequestId) {
|
||||||
|
request(_contactsStatusesRequestId).cancel();
|
||||||
|
}
|
||||||
|
_contactsStatusesRequestId = request(MTPcontacts_GetStatuses()).done([this](const MTPVector<MTPContactStatus> &result) {
|
||||||
|
_contactsStatusesRequestId = 0;
|
||||||
|
for_const (auto &item, result.v) {
|
||||||
|
Assert(item.type() == mtpc_contactStatus);
|
||||||
|
auto &data = item.c_contactStatus();
|
||||||
|
if (auto user = App::userLoaded(data.vuser_id.v)) {
|
||||||
|
auto oldOnlineTill = user->onlineTill;
|
||||||
|
auto newOnlineTill = onlineTillFromStatus(data.vstatus, oldOnlineTill);
|
||||||
|
if (oldOnlineTill != newOnlineTill) {
|
||||||
|
user->onlineTill = newOnlineTill;
|
||||||
|
Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserOnlineChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).fail([this](const RPCError &error) {
|
||||||
|
_contactsStatusesRequestId = 0;
|
||||||
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
int ApiWrap::onlineTillFromStatus(const MTPUserStatus &status, int currentOnlineTill) {
|
int ApiWrap::onlineTillFromStatus(const MTPUserStatus &status, int currentOnlineTill) {
|
||||||
@ -4977,6 +5014,95 @@ void ApiWrap::saveSelfBio(const QString &text, FnMut<void()> done) {
|
|||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApiWrap::reloadPrivacy(Privacy::Key key) {
|
||||||
|
if (_privacyRequestIds.contains(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto requestId = request(MTPaccount_GetPrivacy(
|
||||||
|
Privacy::Input(key)
|
||||||
|
)).done([=](const MTPaccount_PrivacyRules &result) {
|
||||||
|
_privacyRequestIds.erase(key);
|
||||||
|
result.match([&](const MTPDaccount_privacyRules &data) {
|
||||||
|
App::feedUsers(data.vusers);
|
||||||
|
pushPrivacy(key, data.vrules.v);
|
||||||
|
});
|
||||||
|
}).fail([=](const RPCError &error) {
|
||||||
|
_privacyRequestIds.erase(key);
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ApiWrap::parsePrivacy(const QVector<MTPPrivacyRule> &rules)
|
||||||
|
-> Privacy {
|
||||||
|
using Option = Privacy::Option;
|
||||||
|
|
||||||
|
// This is simplified version of privacy rules interpretation.
|
||||||
|
// But it should be fine for all the apps
|
||||||
|
// that use the same subset of features.
|
||||||
|
auto result = Privacy();
|
||||||
|
auto optionSet = false;
|
||||||
|
const auto SetOption = [&](Option option) {
|
||||||
|
if (optionSet) return;
|
||||||
|
optionSet = true;
|
||||||
|
result.option = option;
|
||||||
|
};
|
||||||
|
auto &always = result.always;
|
||||||
|
auto &never = result.never;
|
||||||
|
const auto Feed = [&](const MTPPrivacyRule &rule) {
|
||||||
|
rule.match([&](const MTPDprivacyValueAllowAll &) {
|
||||||
|
SetOption(Option::Everyone);
|
||||||
|
}, [&](const MTPDprivacyValueAllowContacts &) {
|
||||||
|
SetOption(Option::Contacts);
|
||||||
|
}, [&](const MTPDprivacyValueAllowUsers &data) {
|
||||||
|
const auto &users = data.vusers.v;
|
||||||
|
always.reserve(always.size() + users.size());
|
||||||
|
for (const auto userId : users) {
|
||||||
|
const auto user = App::user(UserId(userId.v));
|
||||||
|
if (!base::contains(never, user)
|
||||||
|
&& !base::contains(always, user)) {
|
||||||
|
always.push_back(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [&](const MTPDprivacyValueDisallowContacts &) {
|
||||||
|
// not supported
|
||||||
|
}, [&](const MTPDprivacyValueDisallowAll &) {
|
||||||
|
SetOption(Option::Nobody);
|
||||||
|
}, [&](const MTPDprivacyValueDisallowUsers &data) {
|
||||||
|
const auto &users = data.vusers.v;
|
||||||
|
never.reserve(never.size() + users.size());
|
||||||
|
for (const auto userId : users) {
|
||||||
|
const auto user = App::user(UserId(userId.v));
|
||||||
|
if (!base::contains(always, user)
|
||||||
|
&& !base::contains(never, user)) {
|
||||||
|
never.push_back(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
for (const auto &rule : rules) {
|
||||||
|
Feed(rule);
|
||||||
|
}
|
||||||
|
Feed(MTP_privacyValueDisallowAll()); // disallow by default.
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiWrap::pushPrivacy(
|
||||||
|
Privacy::Key key,
|
||||||
|
const QVector<MTPPrivacyRule> &rules) {
|
||||||
|
const auto &saved = (_privacyValues[key] = parsePrivacy(rules));
|
||||||
|
const auto i = _privacyChanges.find(key);
|
||||||
|
if (i != end(_privacyChanges)) {
|
||||||
|
i->second.fire_copy(saved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ApiWrap::privacyValue(Privacy::Key key) -> rpl::producer<Privacy> {
|
||||||
|
if (const auto i = _privacyValues.find(key); i != end(_privacyValues)) {
|
||||||
|
return _privacyChanges[key].events_starting_with_copy(i->second);
|
||||||
|
} else {
|
||||||
|
return _privacyChanges[key].events();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ApiWrap::readServerHistory(not_null<History*> history) {
|
void ApiWrap::readServerHistory(not_null<History*> history) {
|
||||||
if (history->unreadCount()) {
|
if (history->unreadCount()) {
|
||||||
readServerHistoryForce(history);
|
readServerHistoryForce(history);
|
||||||
|
@ -338,6 +338,26 @@ public:
|
|||||||
|
|
||||||
void saveSelfBio(const QString &text, FnMut<void()> done);
|
void saveSelfBio(const QString &text, FnMut<void()> done);
|
||||||
|
|
||||||
|
struct Privacy {
|
||||||
|
enum class Key {
|
||||||
|
LastSeen,
|
||||||
|
Calls,
|
||||||
|
Invites,
|
||||||
|
};
|
||||||
|
enum class Option {
|
||||||
|
Everyone,
|
||||||
|
Contacts,
|
||||||
|
Nobody,
|
||||||
|
};
|
||||||
|
Option option = Option::Everyone;
|
||||||
|
std::vector<not_null<UserData*>> always;
|
||||||
|
std::vector<not_null<UserData*>> never;
|
||||||
|
|
||||||
|
static MTPInputPrivacyKey Input(Key key);
|
||||||
|
};
|
||||||
|
void reloadPrivacy(Privacy::Key key);
|
||||||
|
rpl::producer<Privacy> privacyValue(Privacy::Key key);
|
||||||
|
|
||||||
~ApiWrap();
|
~ApiWrap();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -521,6 +541,12 @@ private:
|
|||||||
|
|
||||||
void photoUploadReady(const FullMsgId &msgId, const MTPInputFile &file);
|
void photoUploadReady(const FullMsgId &msgId, const MTPInputFile &file);
|
||||||
|
|
||||||
|
Privacy parsePrivacy(const QVector<MTPPrivacyRule> &rules);
|
||||||
|
void pushPrivacy(
|
||||||
|
Privacy::Key key,
|
||||||
|
const QVector<MTPPrivacyRule> &rules);
|
||||||
|
void updatePrivacyLastSeens(const QVector<MTPPrivacyRule> &rules);
|
||||||
|
|
||||||
not_null<AuthSession*> _session;
|
not_null<AuthSession*> _session;
|
||||||
|
|
||||||
MessageDataRequests _messageDataRequests;
|
MessageDataRequests _messageDataRequests;
|
||||||
@ -678,4 +704,8 @@ private:
|
|||||||
FnMut<void()> _saveBioDone;
|
FnMut<void()> _saveBioDone;
|
||||||
QString _saveBioText;
|
QString _saveBioText;
|
||||||
|
|
||||||
|
base::flat_map<Privacy::Key, mtpRequestId> _privacyRequestIds;
|
||||||
|
base::flat_map<Privacy::Key, Privacy> _privacyValues;
|
||||||
|
std::map<Privacy::Key, rpl::event_stream<Privacy>> _privacyChanges;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -14,9 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "ui/wrap/slide_wrap.h"
|
#include "ui/wrap/slide_wrap.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "boxes/peer_list_controllers.h"
|
#include "boxes/peer_list_controllers.h"
|
||||||
|
#include "base/binary_guard.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "auth_session.h"
|
#include "auth_session.h"
|
||||||
#include "lang/lang_keys.h"
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@ -77,9 +78,19 @@ std::unique_ptr<PrivacyExceptionsBoxController::Row> PrivacyExceptionsBoxControl
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
EditPrivacyBox::EditPrivacyBox(QWidget*, std::unique_ptr<Controller> controller) : BoxContent()
|
EditPrivacyBox::EditPrivacyBox(
|
||||||
, _controller(std::move(controller))
|
QWidget*,
|
||||||
|
std::unique_ptr<Controller> controller,
|
||||||
|
rpl::producer<Value> preloaded)
|
||||||
|
: _controller(std::move(controller))
|
||||||
, _loading(this, lang(lng_contacts_loading), Ui::FlatLabel::InitType::Simple, st::membersAbout) {
|
, _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());
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditPrivacyBox::prepare() {
|
void EditPrivacyBox::prepare() {
|
||||||
@ -88,7 +99,11 @@ void EditPrivacyBox::prepare() {
|
|||||||
setTitle([this] { return _controller->title(); });
|
setTitle([this] { return _controller->title(); });
|
||||||
addButton(langFactory(lng_cancel), [this] { closeBox(); });
|
addButton(langFactory(lng_cancel), [this] { closeBox(); });
|
||||||
|
|
||||||
loadData();
|
if (_loading) {
|
||||||
|
_prepared = true;
|
||||||
|
} else {
|
||||||
|
createWidgets();
|
||||||
|
}
|
||||||
|
|
||||||
setDimensions(st::boxWideWidth, countDefaultHeight(st::boxWideWidth));
|
setDimensions(st::boxWideWidth, countDefaultHeight(st::boxWideWidth));
|
||||||
}
|
}
|
||||||
@ -215,13 +230,13 @@ QVector<MTPInputPrivacyRule> EditPrivacyBox::collectResult() {
|
|||||||
constexpr auto kMaxRules = 3; // allow users, disallow users, option
|
constexpr auto kMaxRules = 3; // allow users, disallow users, option
|
||||||
auto result = QVector<MTPInputPrivacyRule>();
|
auto result = QVector<MTPInputPrivacyRule>();
|
||||||
result.reserve(kMaxRules);
|
result.reserve(kMaxRules);
|
||||||
if (showExceptionLink(Exception::Always) && !_alwaysUsers.empty()) {
|
if (showExceptionLink(Exception::Always) && !_value.always.empty()) {
|
||||||
result.push_back(MTP_inputPrivacyValueAllowUsers(MTP_vector<MTPInputUser>(collectInputUsers(_alwaysUsers))));
|
result.push_back(MTP_inputPrivacyValueAllowUsers(MTP_vector<MTPInputUser>(collectInputUsers(_value.always))));
|
||||||
}
|
}
|
||||||
if (showExceptionLink(Exception::Never) && !_neverUsers.empty()) {
|
if (showExceptionLink(Exception::Never) && !_value.never.empty()) {
|
||||||
result.push_back(MTP_inputPrivacyValueDisallowUsers(MTP_vector<MTPInputUser>(collectInputUsers(_neverUsers))));
|
result.push_back(MTP_inputPrivacyValueDisallowUsers(MTP_vector<MTPInputUser>(collectInputUsers(_value.never))));
|
||||||
}
|
}
|
||||||
switch (_option) {
|
switch (_value.option) {
|
||||||
case Option::Everyone: result.push_back(MTP_inputPrivacyValueAllowAll()); break;
|
case Option::Everyone: result.push_back(MTP_inputPrivacyValueAllowAll()); break;
|
||||||
case Option::Contacts: result.push_back(MTP_inputPrivacyValueAllowContacts()); break;
|
case Option::Contacts: result.push_back(MTP_inputPrivacyValueAllowContacts()); break;
|
||||||
case Option::Nobody: result.push_back(MTP_inputPrivacyValueDisallowAll()); break;
|
case Option::Nobody: result.push_back(MTP_inputPrivacyValueDisallowAll()); break;
|
||||||
@ -236,8 +251,8 @@ style::margins EditPrivacyBox::exceptionLinkMargins() const {
|
|||||||
|
|
||||||
std::vector<not_null<UserData*>> &EditPrivacyBox::exceptionUsers(Exception exception) {
|
std::vector<not_null<UserData*>> &EditPrivacyBox::exceptionUsers(Exception exception) {
|
||||||
switch (exception) {
|
switch (exception) {
|
||||||
case Exception::Always: return _alwaysUsers;
|
case Exception::Always: return _value.always;
|
||||||
case Exception::Never: return _neverUsers;
|
case Exception::Never: return _value.never;
|
||||||
}
|
}
|
||||||
Unexpected("Invalid exception value.");
|
Unexpected("Invalid exception value.");
|
||||||
}
|
}
|
||||||
@ -252,18 +267,18 @@ object_ptr<Ui::SlideWrap<Ui::LinkButton>> &EditPrivacyBox::exceptionLink(Excepti
|
|||||||
|
|
||||||
bool EditPrivacyBox::showExceptionLink(Exception exception) const {
|
bool EditPrivacyBox::showExceptionLink(Exception exception) const {
|
||||||
switch (exception) {
|
switch (exception) {
|
||||||
case Exception::Always: return (_option == Option::Contacts) || (_option == Option::Nobody);
|
case Exception::Always: return (_value.option == Option::Contacts) || (_value.option == Option::Nobody);
|
||||||
case Exception::Never: return (_option == Option::Everyone) || (_option == Option::Contacts);
|
case Exception::Never: return (_value.option == Option::Everyone) || (_value.option == Option::Contacts);
|
||||||
}
|
}
|
||||||
Unexpected("Invalid exception value.");
|
Unexpected("Invalid exception value.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditPrivacyBox::createWidgets() {
|
void EditPrivacyBox::createWidgets() {
|
||||||
_loading.destroy();
|
_loading.destroy();
|
||||||
_optionGroup = std::make_shared<Ui::RadioenumGroup<Option>>(_option);
|
_optionGroup = std::make_shared<Ui::RadioenumGroup<Option>>(_value.option);
|
||||||
|
|
||||||
auto createOption = [this](object_ptr<Ui::Radioenum<Option>> &widget, Option option, const QString &label) {
|
auto createOption = [this](object_ptr<Ui::Radioenum<Option>> &widget, Option option, const QString &label) {
|
||||||
if (_controller->hasOption(option) || (_option == option)) {
|
if (_controller->hasOption(option) || (_value.option == option)) {
|
||||||
widget.create(this, _optionGroup, option, label, st::defaultBoxCheckbox);
|
widget.create(this, _optionGroup, option, label, st::defaultBoxCheckbox);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -294,16 +309,18 @@ void EditPrivacyBox::createWidgets() {
|
|||||||
|
|
||||||
clearButtons();
|
clearButtons();
|
||||||
addButton(langFactory(lng_settings_save), [this] {
|
addButton(langFactory(lng_settings_save), [this] {
|
||||||
auto someAreDisallowed = (_option != Option::Everyone) || !_neverUsers.empty();
|
auto someAreDisallowed = (_value.option != Option::Everyone) || !_value.never.empty();
|
||||||
_controller->confirmSave(someAreDisallowed, crl::guard(this, [this] {
|
_controller->confirmSave(someAreDisallowed, crl::guard(this, [this] {
|
||||||
Auth().api().savePrivacy(_controller->key(), collectResult());
|
Auth().api().savePrivacy(
|
||||||
|
_controller->apiKey(),
|
||||||
|
collectResult());
|
||||||
closeBox();
|
closeBox();
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
addButton(langFactory(lng_cancel), [this] { closeBox(); });
|
addButton(langFactory(lng_cancel), [this] { closeBox(); });
|
||||||
|
|
||||||
_optionGroup->setChangedCallback([this](Option value) {
|
_optionGroup->setChangedCallback([this](Option value) {
|
||||||
_option = value;
|
_value.option = value;
|
||||||
_alwaysLink->toggle(
|
_alwaysLink->toggle(
|
||||||
showExceptionLink(Exception::Always),
|
showExceptionLink(Exception::Always),
|
||||||
anim::type::normal);
|
anim::type::normal);
|
||||||
@ -323,54 +340,10 @@ void EditPrivacyBox::createWidgets() {
|
|||||||
setDimensions(st::boxWideWidth, resizeGetHeight(st::boxWideWidth));
|
setDimensions(st::boxWideWidth, resizeGetHeight(st::boxWideWidth));
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditPrivacyBox::loadData() {
|
void EditPrivacyBox::dataReady(Value &&value) {
|
||||||
request(MTPaccount_GetPrivacy(_controller->key())).done([this](const MTPaccount_PrivacyRules &result) {
|
_value = std::move(value);
|
||||||
Expects(result.type() == mtpc_account_privacyRules);
|
_loading.destroy();
|
||||||
auto &rules = result.c_account_privacyRules();
|
if (_prepared) {
|
||||||
App::feedUsers(rules.vusers);
|
|
||||||
|
|
||||||
// This is simplified version of privacy rules interpretation.
|
|
||||||
// But it should be fine for all the apps that use the same subset of features.
|
|
||||||
auto optionSet = false;
|
|
||||||
auto setOption = [this, &optionSet](Option option) {
|
|
||||||
if (optionSet) return;
|
|
||||||
optionSet = true;
|
|
||||||
_option = option;
|
|
||||||
};
|
|
||||||
auto feedRule = [this, &setOption](const MTPPrivacyRule &rule) {
|
|
||||||
switch (rule.type()) {
|
|
||||||
case mtpc_privacyValueAllowAll: setOption(Option::Everyone); break;
|
|
||||||
case mtpc_privacyValueAllowContacts: setOption(Option::Contacts); break;
|
|
||||||
case mtpc_privacyValueAllowUsers: {
|
|
||||||
auto &users = rule.c_privacyValueAllowUsers().vusers.v;
|
|
||||||
_alwaysUsers.reserve(_alwaysUsers.size() + users.size());
|
|
||||||
for (auto &userId : users) {
|
|
||||||
auto user = App::user(UserId(userId.v));
|
|
||||||
if (!base::contains(_neverUsers, user) && !base::contains(_alwaysUsers, user)) {
|
|
||||||
_alwaysUsers.push_back(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
case mtpc_privacyValueDisallowContacts: // not supported, fall through
|
|
||||||
case mtpc_privacyValueDisallowAll: setOption(Option::Nobody); break;
|
|
||||||
case mtpc_privacyValueDisallowUsers: {
|
|
||||||
auto &users = rule.c_privacyValueDisallowUsers().vusers.v;
|
|
||||||
_neverUsers.reserve(_neverUsers.size() + users.size());
|
|
||||||
for (auto &userId : users) {
|
|
||||||
auto user = App::user(UserId(userId.v));
|
|
||||||
if (!base::contains(_alwaysUsers, user) && !base::contains(_neverUsers, user)) {
|
|
||||||
_neverUsers.push_back(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
for (auto &rule : rules.vrules.v) {
|
|
||||||
feedRule(rule);
|
|
||||||
}
|
|
||||||
feedRule(MTP_privacyValueDisallowAll()); // disallow by default.
|
|
||||||
|
|
||||||
createWidgets();
|
createWidgets();
|
||||||
}).send();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
|
|
||||||
#include "boxes/abstract_box.h"
|
#include "boxes/abstract_box.h"
|
||||||
#include "mtproto/sender.h"
|
#include "mtproto/sender.h"
|
||||||
|
#include "apiwrap.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class FlatLabel;
|
class FlatLabel;
|
||||||
@ -23,11 +24,8 @@ class SlideWrap;
|
|||||||
|
|
||||||
class EditPrivacyBox : public BoxContent, private MTP::Sender {
|
class EditPrivacyBox : public BoxContent, private MTP::Sender {
|
||||||
public:
|
public:
|
||||||
enum class Option {
|
using Value = ApiWrap::Privacy;
|
||||||
Everyone,
|
using Option = Value::Option;
|
||||||
Contacts,
|
|
||||||
Nobody,
|
|
||||||
};
|
|
||||||
enum class Exception {
|
enum class Exception {
|
||||||
Always,
|
Always,
|
||||||
Never,
|
Never,
|
||||||
@ -35,7 +33,10 @@ public:
|
|||||||
|
|
||||||
class Controller {
|
class Controller {
|
||||||
public:
|
public:
|
||||||
virtual MTPInputPrivacyKey key() = 0;
|
using Key = ApiWrap::Privacy::Key;
|
||||||
|
|
||||||
|
virtual Key key() = 0;
|
||||||
|
virtual MTPInputPrivacyKey apiKey() = 0;
|
||||||
|
|
||||||
virtual QString title() = 0;
|
virtual QString title() = 0;
|
||||||
virtual bool hasOption(Option option) {
|
virtual bool hasOption(Option option) {
|
||||||
@ -71,7 +72,10 @@ public:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
EditPrivacyBox(QWidget*, std::unique_ptr<Controller> controller);
|
EditPrivacyBox(
|
||||||
|
QWidget*,
|
||||||
|
std::unique_ptr<Controller> controller,
|
||||||
|
rpl::producer<Value> preloaded);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void prepare() override;
|
void prepare() override;
|
||||||
@ -84,16 +88,18 @@ private:
|
|||||||
bool showExceptionLink(Exception exception) const;
|
bool showExceptionLink(Exception exception) const;
|
||||||
void createWidgets();
|
void createWidgets();
|
||||||
QVector<MTPInputPrivacyRule> collectResult();
|
QVector<MTPInputPrivacyRule> collectResult();
|
||||||
void loadData();
|
void dataReady(Value &&value);
|
||||||
int countDefaultHeight(int newWidth);
|
int countDefaultHeight(int newWidth);
|
||||||
|
|
||||||
void editExceptionUsers(Exception exception);
|
void editExceptionUsers(Exception exception);
|
||||||
QString exceptionLinkText(Exception exception);
|
QString exceptionLinkText(Exception exception);
|
||||||
std::vector<not_null<UserData*>> &exceptionUsers(Exception exception);
|
std::vector<not_null<UserData*>> &exceptionUsers(Exception exception);
|
||||||
object_ptr<Ui::SlideWrap<Ui::LinkButton>> &exceptionLink(Exception exception);
|
object_ptr<Ui::SlideWrap<Ui::LinkButton>> &exceptionLink(
|
||||||
|
Exception exception);
|
||||||
|
|
||||||
std::unique_ptr<Controller> _controller;
|
std::unique_ptr<Controller> _controller;
|
||||||
Option _option = Option::Everyone;
|
Value _value;
|
||||||
|
bool _prepared = false;
|
||||||
|
|
||||||
std::shared_ptr<Ui::RadioenumGroup<Option>> _optionGroup;
|
std::shared_ptr<Ui::RadioenumGroup<Option>> _optionGroup;
|
||||||
object_ptr<Ui::FlatLabel> _loading;
|
object_ptr<Ui::FlatLabel> _loading;
|
||||||
@ -107,7 +113,4 @@ private:
|
|||||||
object_ptr<Ui::SlideWrap<Ui::LinkButton>> _neverLink = { nullptr };
|
object_ptr<Ui::SlideWrap<Ui::LinkButton>> _neverLink = { nullptr };
|
||||||
object_ptr<Ui::FlatLabel> _exceptionsDescription = { nullptr };
|
object_ptr<Ui::FlatLabel> _exceptionsDescription = { nullptr };
|
||||||
|
|
||||||
std::vector<not_null<UserData*>> _alwaysUsers;
|
|
||||||
std::vector<not_null<UserData*>> _neverUsers;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -220,7 +220,11 @@ std::unique_ptr<PeerListRow> BlockedBoxController::createRow(UserData *user) con
|
|||||||
return std::move(row);
|
return std::move(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
MTPInputPrivacyKey LastSeenPrivacyController::key() {
|
ApiWrap::Privacy::Key LastSeenPrivacyController::key() {
|
||||||
|
return Key::LastSeen;
|
||||||
|
}
|
||||||
|
|
||||||
|
MTPInputPrivacyKey LastSeenPrivacyController::apiKey() {
|
||||||
return MTP_inputPrivacyKeyStatusTimestamp();
|
return MTP_inputPrivacyKeyStatusTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,7 +278,11 @@ void LastSeenPrivacyController::confirmSave(bool someAreDisallowed, FnMut<void()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MTPInputPrivacyKey GroupsInvitePrivacyController::key() {
|
ApiWrap::Privacy::Key GroupsInvitePrivacyController::key() {
|
||||||
|
return Key::Invites;
|
||||||
|
}
|
||||||
|
|
||||||
|
MTPInputPrivacyKey GroupsInvitePrivacyController::apiKey() {
|
||||||
return MTP_inputPrivacyKeyChatInvite();
|
return MTP_inputPrivacyKeyChatInvite();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,7 +318,11 @@ QString GroupsInvitePrivacyController::exceptionsDescription() {
|
|||||||
return lang(lng_edit_privacy_groups_exceptions);
|
return lang(lng_edit_privacy_groups_exceptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
MTPInputPrivacyKey CallsPrivacyController::key() {
|
ApiWrap::Privacy::Key CallsPrivacyController::key() {
|
||||||
|
return Key::Calls;
|
||||||
|
}
|
||||||
|
|
||||||
|
MTPInputPrivacyKey CallsPrivacyController::apiKey() {
|
||||||
return MTP_inputPrivacyKeyPhoneCall();
|
return MTP_inputPrivacyKeyPhoneCall();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,8 @@ public:
|
|||||||
using Option = EditPrivacyBox::Option;
|
using Option = EditPrivacyBox::Option;
|
||||||
using Exception = EditPrivacyBox::Exception;
|
using Exception = EditPrivacyBox::Exception;
|
||||||
|
|
||||||
MTPInputPrivacyKey key() override;
|
Key key() override;
|
||||||
|
MTPInputPrivacyKey apiKey() override;
|
||||||
|
|
||||||
QString title() override;
|
QString title() override;
|
||||||
QString description() override;
|
QString description() override;
|
||||||
@ -59,7 +60,8 @@ public:
|
|||||||
using Option = EditPrivacyBox::Option;
|
using Option = EditPrivacyBox::Option;
|
||||||
using Exception = EditPrivacyBox::Exception;
|
using Exception = EditPrivacyBox::Exception;
|
||||||
|
|
||||||
MTPInputPrivacyKey key() override;
|
Key key() override;
|
||||||
|
MTPInputPrivacyKey apiKey() override;
|
||||||
|
|
||||||
QString title() override;
|
QString title() override;
|
||||||
bool hasOption(Option option) override;
|
bool hasOption(Option option) override;
|
||||||
@ -75,7 +77,8 @@ public:
|
|||||||
using Option = EditPrivacyBox::Option;
|
using Option = EditPrivacyBox::Option;
|
||||||
using Exception = EditPrivacyBox::Exception;
|
using Exception = EditPrivacyBox::Exception;
|
||||||
|
|
||||||
MTPInputPrivacyKey key() override;
|
Key key() override;
|
||||||
|
MTPInputPrivacyKey apiKey() override;
|
||||||
|
|
||||||
QString title() override;
|
QString title() override;
|
||||||
QString description() override;
|
QString description() override;
|
||||||
|
@ -286,15 +286,12 @@ void PrivacyWidget::onBlockedUsers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PrivacyWidget::onLastSeenPrivacy() {
|
void PrivacyWidget::onLastSeenPrivacy() {
|
||||||
Ui::show(Box<EditPrivacyBox>(std::make_unique<LastSeenPrivacyController>()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrivacyWidget::onCallsPrivacy() {
|
void PrivacyWidget::onCallsPrivacy() {
|
||||||
Ui::show(Box<EditPrivacyBox>(std::make_unique<CallsPrivacyController>()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrivacyWidget::onGroupsInvitePrivacy() {
|
void PrivacyWidget::onGroupsInvitePrivacy() {
|
||||||
Ui::show(Box<EditPrivacyBox>(std::make_unique<GroupsInvitePrivacyController>()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrivacyWidget::onAutoLock() {
|
void PrivacyWidget::onAutoLock() {
|
||||||
|
@ -65,30 +65,60 @@ void SetupPrivacy(not_null<Ui::VerticalLayout*> container) {
|
|||||||
initBox));
|
initBox));
|
||||||
});
|
});
|
||||||
|
|
||||||
AddButton(
|
using Privacy = ApiWrap::Privacy;
|
||||||
container,
|
const auto PrivacyString = [](Privacy::Key key) {
|
||||||
|
Auth().api().reloadPrivacy(key);
|
||||||
|
return Auth().api().privacyValue(
|
||||||
|
key
|
||||||
|
) | rpl::map([](const Privacy &value) {
|
||||||
|
const auto base = [&] {
|
||||||
|
using Option = Privacy::Option;
|
||||||
|
switch (value.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("Value in Privacy::Option.");
|
||||||
|
}();
|
||||||
|
auto add = QStringList();
|
||||||
|
if (const auto never = value.never.size()) {
|
||||||
|
add.push_back("-" + QString::number(never));
|
||||||
|
}
|
||||||
|
if (const auto always = value.always.size()) {
|
||||||
|
add.push_back("+" + QString::number(always));
|
||||||
|
}
|
||||||
|
if (!add.isEmpty()) {
|
||||||
|
return lang(base) + " (" + add.join(", ") + ")";
|
||||||
|
} else {
|
||||||
|
return lang(base);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
using namespace OldSettings;
|
||||||
|
const auto add = [&](LangKey label, Privacy::Key key, auto controller) {
|
||||||
|
AddButtonWithLabel(
|
||||||
|
container,
|
||||||
|
label,
|
||||||
|
PrivacyString(key),
|
||||||
|
st::settingsButton
|
||||||
|
)->addClickHandler([=] {
|
||||||
|
Ui::show(Box<EditPrivacyBox>(
|
||||||
|
controller(),
|
||||||
|
Auth().api().privacyValue(key)));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
add(
|
||||||
lng_settings_last_seen,
|
lng_settings_last_seen,
|
||||||
st::settingsButton
|
Privacy::Key::LastSeen,
|
||||||
)->addClickHandler([] {
|
[] { return std::make_unique<LastSeenPrivacyController>(); });
|
||||||
Ui::show(Box<EditPrivacyBox>(
|
add(
|
||||||
std::make_unique<OldSettings::LastSeenPrivacyController>()));
|
|
||||||
});
|
|
||||||
AddButton(
|
|
||||||
container,
|
|
||||||
lng_settings_calls,
|
lng_settings_calls,
|
||||||
st::settingsButton
|
Privacy::Key::Calls,
|
||||||
)->addClickHandler([] {
|
[] { return std::make_unique<CallsPrivacyController>(); });
|
||||||
Ui::show(Box<EditPrivacyBox>(
|
add(
|
||||||
std::make_unique<OldSettings::CallsPrivacyController>()));
|
|
||||||
});
|
|
||||||
AddButton(
|
|
||||||
container,
|
|
||||||
lng_settings_groups_invite,
|
lng_settings_groups_invite,
|
||||||
st::settingsButton
|
Privacy::Key::Invites,
|
||||||
)->addClickHandler([] {
|
[] { return std::make_unique<GroupsInvitePrivacyController>(); });
|
||||||
Ui::show(Box<EditPrivacyBox>(
|
|
||||||
std::make_unique<OldSettings::GroupsInvitePrivacyController>()));
|
|
||||||
});
|
|
||||||
|
|
||||||
AddSkip(container, st::settingsPrivacySecurityPadding);
|
AddSkip(container, st::settingsPrivacySecurityPadding);
|
||||||
AddDividerText(
|
AddDividerText(
|
||||||
|
Loading…
Reference in New Issue
Block a user