Implement new permanent invite link management.
BIN
Telegram/Resources/icons/info/edit/group_manage_actions.png
Normal file
After Width: | Height: | Size: 430 B |
BIN
Telegram/Resources/icons/info/edit/group_manage_actions@2x.png
Normal file
After Width: | Height: | Size: 780 B |
BIN
Telegram/Resources/icons/info/edit/group_manage_actions@3x.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/info/edit/group_manage_admins.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/info/edit/group_manage_admins@2x.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
Telegram/Resources/icons/info/edit/group_manage_admins@3x.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
Telegram/Resources/icons/info/edit/group_manage_links.png
Normal file
After Width: | Height: | Size: 933 B |
BIN
Telegram/Resources/icons/info/edit/group_manage_links@2x.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/info/edit/group_manage_links@3x.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
Telegram/Resources/icons/info/edit/group_manage_members.png
Normal file
After Width: | Height: | Size: 620 B |
BIN
Telegram/Resources/icons/info/edit/group_manage_members@2x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/info/edit/group_manage_members@3x.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
Telegram/Resources/icons/info/edit/group_manage_permissions.png
Normal file
After Width: | Height: | Size: 980 B |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.7 KiB |
|
@ -934,7 +934,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_manage_peer_permissions" = "Permissions";
|
"lng_manage_peer_permissions" = "Permissions";
|
||||||
"lng_manage_peer_invite_links" = "Invite links";
|
"lng_manage_peer_invite_links" = "Invite links";
|
||||||
|
|
||||||
|
|
||||||
"lng_manage_peer_group_type" = "Group type";
|
"lng_manage_peer_group_type" = "Group type";
|
||||||
"lng_manage_peer_channel_type" = "Channel type";
|
"lng_manage_peer_channel_type" = "Channel type";
|
||||||
"lng_manage_peer_link_type" = "Link type";
|
"lng_manage_peer_link_type" = "Link type";
|
||||||
|
|
|
@ -21,6 +21,23 @@ namespace {
|
||||||
constexpr auto kFirstPage = 10;
|
constexpr auto kFirstPage = 10;
|
||||||
constexpr auto kPerPage = 50;
|
constexpr auto kPerPage = 50;
|
||||||
|
|
||||||
|
void BringPermanentToFront(PeerInviteLinks &links) {
|
||||||
|
auto &list = links.links;
|
||||||
|
const auto i = ranges::find_if(list, [](const InviteLink &link) {
|
||||||
|
return link.permanent && !link.revoked;
|
||||||
|
});
|
||||||
|
if (i != end(list) && i != begin(list)) {
|
||||||
|
ranges::rotate(begin(list), i, i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemovePermanent(PeerInviteLinks &links) {
|
||||||
|
auto &list = links.links;
|
||||||
|
list.erase(ranges::remove_if(list, [](const InviteLink &link) {
|
||||||
|
return link.permanent && !link.revoked;
|
||||||
|
}), end(list));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
InviteLinks::InviteLinks(not_null<ApiWrap*> api) : _api(api) {
|
InviteLinks::InviteLinks(not_null<ApiWrap*> api) : _api(api) {
|
||||||
|
@ -28,93 +45,167 @@ InviteLinks::InviteLinks(not_null<ApiWrap*> api) : _api(api) {
|
||||||
|
|
||||||
void InviteLinks::create(
|
void InviteLinks::create(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
|
Fn<void(Link)> done,
|
||||||
TimeId expireDate,
|
TimeId expireDate,
|
||||||
int usageLimit) {
|
int usageLimit) {
|
||||||
if (_createRequests.contains(peer)) {
|
performCreate(peer, std::move(done), false, expireDate, usageLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InviteLinks::performCreate(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
Fn<void(Link)> done,
|
||||||
|
bool revokeLegacyPermanent,
|
||||||
|
TimeId expireDate,
|
||||||
|
int usageLimit) {
|
||||||
|
if (const auto i = _createCallbacks.find(peer)
|
||||||
|
; i != end(_createCallbacks)) {
|
||||||
|
if (done) {
|
||||||
|
i->second.push_back(std::move(done));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
auto &callbacks = _createCallbacks[peer];
|
||||||
|
if (done) {
|
||||||
|
callbacks.push_back(std::move(done));
|
||||||
|
}
|
||||||
|
|
||||||
using Flag = MTPmessages_ExportChatInvite::Flag;
|
using Flag = MTPmessages_ExportChatInvite::Flag;
|
||||||
const auto requestId = _api->request(MTPmessages_ExportChatInvite(
|
_api->request(MTPmessages_ExportChatInvite(
|
||||||
MTP_flags((expireDate ? Flag::f_expire_date : Flag(0))
|
MTP_flags((expireDate ? Flag::f_expire_date : Flag(0))
|
||||||
| (usageLimit ? Flag::f_usage_limit : Flag(0))),
|
| (usageLimit ? Flag::f_usage_limit : Flag(0))),
|
||||||
peer->input,
|
peer->input,
|
||||||
MTP_int(expireDate),
|
MTP_int(expireDate),
|
||||||
MTP_int(usageLimit)
|
MTP_int(usageLimit)
|
||||||
)).done([=](const MTPExportedChatInvite &result) {
|
)).done([=](const MTPExportedChatInvite &result) {
|
||||||
_createRequests.erase(peer);
|
const auto callbacks = _createCallbacks.take(peer);
|
||||||
const auto link = (result.type() == mtpc_chatInviteExported)
|
const auto link = prepend(peer, result);
|
||||||
? qs(result.c_chatInviteExported().vlink())
|
if (callbacks) {
|
||||||
: QString();
|
for (const auto &callback : *callbacks) {
|
||||||
if (!expireDate && !usageLimit) {
|
callback(link);
|
||||||
editPermanentLink(peer, QString(), link);
|
}
|
||||||
}
|
}
|
||||||
}).fail([=](const RPCError &error) {
|
}).fail([=](const RPCError &error) {
|
||||||
_createRequests.erase(peer);
|
_createCallbacks.erase(peer);
|
||||||
}).send();
|
}).send();
|
||||||
_createRequests.emplace(peer, requestId);
|
}
|
||||||
|
|
||||||
|
auto InviteLinks::lookupPermanent(not_null<PeerData*> peer) -> Link* {
|
||||||
|
auto i = _firstSlices.find(peer);
|
||||||
|
return (i != end(_firstSlices)) ? lookupPermanent(i->second) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InviteLinks::lookupPermanent(Links &links) -> Link* {
|
||||||
|
const auto first = links.links.begin();
|
||||||
|
return (first != end(links.links) && first->permanent && !first->revoked)
|
||||||
|
? &*first
|
||||||
|
: nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InviteLinks::lookupPermanent(const Links &links) const -> const Link* {
|
||||||
|
const auto first = links.links.begin();
|
||||||
|
return (first != end(links.links) && first->permanent && !first->revoked)
|
||||||
|
? &*first
|
||||||
|
: nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InviteLinks::prepend(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
const MTPExportedChatInvite &invite) -> Link {
|
||||||
|
const auto link = parse(peer, invite);
|
||||||
|
auto i = _firstSlices.find(peer);
|
||||||
|
if (i == end(_firstSlices)) {
|
||||||
|
i = _firstSlices.emplace(peer).first;
|
||||||
|
}
|
||||||
|
auto &links = i->second;
|
||||||
|
if (link.permanent) {
|
||||||
|
if (const auto permanent = lookupPermanent(links)) {
|
||||||
|
permanent->revoked = true;
|
||||||
|
}
|
||||||
|
editPermanentLink(peer, link.link);
|
||||||
|
}
|
||||||
|
++links.count;
|
||||||
|
links.links.insert(begin(links.links), link);
|
||||||
|
notify(peer);
|
||||||
|
return link;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InviteLinks::edit(
|
void InviteLinks::edit(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const QString &link,
|
const QString &link,
|
||||||
TimeId expireDate,
|
TimeId expireDate,
|
||||||
|
int usageLimit,
|
||||||
|
Fn<void(Link)> done) {
|
||||||
|
performEdit(peer, link, std::move(done), false, expireDate, usageLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InviteLinks::performEdit(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
const QString &link,
|
||||||
|
Fn<void(Link)> done,
|
||||||
|
bool revoke,
|
||||||
|
TimeId expireDate,
|
||||||
int usageLimit) {
|
int usageLimit) {
|
||||||
const auto key = EditKey{ peer, link };
|
const auto key = EditKey{ peer, link };
|
||||||
if (_editRequests.contains(key)) {
|
if (const auto i = _editCallbacks.find(key); i != end(_editCallbacks)) {
|
||||||
|
if (done) {
|
||||||
|
i->second.push_back(std::move(done));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
auto &callbacks = _editCallbacks[key];
|
||||||
|
if (done) {
|
||||||
|
callbacks.push_back(std::move(done));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto permanent = revoke ? lookupPermanent(peer) : nullptr) {
|
||||||
|
if (permanent->link == link) {
|
||||||
|
// In case of revoking a permanent link
|
||||||
|
// we should just create a new one instead.
|
||||||
|
performCreate(peer, std::move(done), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
using Flag = MTPmessages_EditExportedChatInvite::Flag;
|
using Flag = MTPmessages_EditExportedChatInvite::Flag;
|
||||||
const auto requestId = _api->request(MTPmessages_EditExportedChatInvite(
|
const auto requestId = _api->request(MTPmessages_EditExportedChatInvite(
|
||||||
MTP_flags((expireDate ? Flag::f_expire_date : Flag(0))
|
MTP_flags((revoke ? Flag::f_revoked : Flag(0))
|
||||||
| (usageLimit ? Flag::f_usage_limit : Flag(0))),
|
| ((!revoke && expireDate) ? Flag::f_expire_date : Flag(0))
|
||||||
|
| ((!revoke && usageLimit) ? Flag::f_usage_limit : Flag(0))),
|
||||||
peer->input,
|
peer->input,
|
||||||
MTP_string(link),
|
MTP_string(link),
|
||||||
MTP_int(expireDate),
|
MTP_int(expireDate),
|
||||||
MTP_int(usageLimit)
|
MTP_int(usageLimit)
|
||||||
)).done([=](const MTPmessages_ExportedChatInvite &result) {
|
)).done([=](const MTPmessages_ExportedChatInvite &result) {
|
||||||
_editRequests.erase(key);
|
const auto callbacks = _editCallbacks.take(key);
|
||||||
|
const auto peer = key.peer;
|
||||||
result.match([&](const MTPDmessages_exportedChatInvite &data) {
|
result.match([&](const MTPDmessages_exportedChatInvite &data) {
|
||||||
_api->session().data().processUsers(data.vusers());
|
_api->session().data().processUsers(data.vusers());
|
||||||
const auto &invite = data.vinvite();
|
const auto link = parse(peer, data.vinvite());
|
||||||
const auto link = (invite.type() == mtpc_chatInviteExported)
|
auto i = _firstSlices.find(peer);
|
||||||
? qs(invite.c_chatInviteExported().vlink())
|
if (i != end(_firstSlices)) {
|
||||||
: QString();
|
const auto j = ranges::find(
|
||||||
// #TODO links
|
i->second.links,
|
||||||
|
key.link,
|
||||||
|
&Link::link);
|
||||||
|
if (j != end(i->second.links)) {
|
||||||
|
*j = link;
|
||||||
|
notify(peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &callback : *callbacks) {
|
||||||
|
callback(link);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}).fail([=](const RPCError &error) {
|
}).fail([=](const RPCError &error) {
|
||||||
_editRequests.erase(key);
|
_editCallbacks.erase(key);
|
||||||
}).send();
|
}).send();
|
||||||
_editRequests.emplace(key, requestId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InviteLinks::revoke(not_null<PeerData*> peer, const QString &link) {
|
void InviteLinks::revoke(
|
||||||
const auto key = EditKey{ peer, link };
|
not_null<PeerData*> peer,
|
||||||
if (_editRequests.contains(key)) {
|
const QString &link,
|
||||||
return;
|
Fn<void(Link)> done) {
|
||||||
}
|
performEdit(peer, link, std::move(done), true);
|
||||||
|
|
||||||
const auto requestId = _api->request(MTPmessages_EditExportedChatInvite(
|
|
||||||
MTP_flags(MTPmessages_EditExportedChatInvite::Flag::f_revoked),
|
|
||||||
peer->input,
|
|
||||||
MTP_string(link),
|
|
||||||
MTPint(), // expire_date
|
|
||||||
MTPint() // usage_limit
|
|
||||||
)).done([=](const MTPmessages_ExportedChatInvite &result) {
|
|
||||||
_editRequests.erase(key);
|
|
||||||
result.match([&](const MTPDmessages_exportedChatInvite &data) {
|
|
||||||
_api->session().data().processUsers(data.vusers());
|
|
||||||
const auto &invite = data.vinvite();
|
|
||||||
const auto link = (invite.type() == mtpc_chatInviteExported)
|
|
||||||
? qs(invite.c_chatInviteExported().vlink())
|
|
||||||
: QString();
|
|
||||||
editPermanentLink(peer, key.link, link);
|
|
||||||
});
|
|
||||||
}).fail([=](const RPCError &error) {
|
|
||||||
_editRequests.erase(key);
|
|
||||||
}).send();
|
|
||||||
_editRequests.emplace(key, requestId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InviteLinks::requestLinks(not_null<PeerData*> peer) {
|
void InviteLinks::requestLinks(not_null<PeerData*> peer) {
|
||||||
|
@ -129,16 +220,80 @@ void InviteLinks::requestLinks(not_null<PeerData*> peer) {
|
||||||
MTP_int(kFirstPage)
|
MTP_int(kFirstPage)
|
||||||
)).done([=](const MTPmessages_ExportedChatInvites &result) {
|
)).done([=](const MTPmessages_ExportedChatInvites &result) {
|
||||||
_firstSliceRequests.remove(peer);
|
_firstSliceRequests.remove(peer);
|
||||||
_firstSlices.emplace_or_assign(peer, parseSlice(peer, result));
|
auto slice = parseSlice(peer, result);
|
||||||
peer->session().changes().peerUpdated(
|
auto i = _firstSlices.find(peer);
|
||||||
peer,
|
const auto permanent = (i != end(_firstSlices))
|
||||||
Data::PeerUpdate::Flag::InviteLink);
|
? lookupPermanent(i->second)
|
||||||
|
: nullptr;
|
||||||
|
if (!permanent) {
|
||||||
|
BringPermanentToFront(slice);
|
||||||
|
const auto j = _firstSlices.emplace_or_assign(
|
||||||
|
peer,
|
||||||
|
std::move(slice)).first;
|
||||||
|
if (const auto permanent = lookupPermanent(j->second)) {
|
||||||
|
editPermanentLink(peer, permanent->link);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RemovePermanent(slice);
|
||||||
|
auto &existing = i->second.links;
|
||||||
|
existing.erase(begin(existing) + 1, end(existing));
|
||||||
|
existing.insert(
|
||||||
|
end(existing),
|
||||||
|
begin(slice.links),
|
||||||
|
end(slice.links));
|
||||||
|
i->second.count = std::max(slice.count, int(existing.size()));
|
||||||
|
}
|
||||||
|
notify(peer);
|
||||||
}).fail([=](const RPCError &error) {
|
}).fail([=](const RPCError &error) {
|
||||||
_firstSliceRequests.remove(peer);
|
_firstSliceRequests.remove(peer);
|
||||||
}).send();
|
}).send();
|
||||||
_firstSliceRequests.emplace(peer, requestId);
|
_firstSliceRequests.emplace(peer, requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InviteLinks::setPermanent(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
const MTPExportedChatInvite &invite) {
|
||||||
|
auto link = parse(peer, invite);
|
||||||
|
link.permanent = true; // #TODO links remove hack
|
||||||
|
//if (!link.permanent) {
|
||||||
|
// LOG(("API Error: "
|
||||||
|
// "InviteLinks::setPermanent called with non-permanent link."));
|
||||||
|
// return;
|
||||||
|
//}
|
||||||
|
auto i = _firstSlices.find(peer);
|
||||||
|
if (i == end(_firstSlices)) {
|
||||||
|
i = _firstSlices.emplace(peer).first;
|
||||||
|
}
|
||||||
|
auto &links = i->second;
|
||||||
|
if (const auto permanent = lookupPermanent(links)) {
|
||||||
|
if (permanent->link == link.link) {
|
||||||
|
if (permanent->usage != link.usage) {
|
||||||
|
permanent->usage = link.usage;
|
||||||
|
notify(peer);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
permanent->revoked = true;
|
||||||
|
}
|
||||||
|
links.links.insert(begin(links.links), link);
|
||||||
|
editPermanentLink(peer, link.link);
|
||||||
|
notify(peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InviteLinks::clearPermanent(not_null<PeerData*> peer) {
|
||||||
|
if (const auto permanent = lookupPermanent(peer)) {
|
||||||
|
permanent->revoked = true;
|
||||||
|
editPermanentLink(peer, QString());
|
||||||
|
notify(peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InviteLinks::notify(not_null<PeerData*> peer) {
|
||||||
|
peer->session().changes().peerUpdated(
|
||||||
|
peer,
|
||||||
|
Data::PeerUpdate::Flag::InviteLinks);
|
||||||
|
}
|
||||||
|
|
||||||
auto InviteLinks::links(not_null<PeerData*> peer) const -> Links {
|
auto InviteLinks::links(not_null<PeerData*> peer) const -> Links {
|
||||||
const auto i = _firstSlices.find(peer);
|
const auto i = _firstSlices.find(peer);
|
||||||
return (i != end(_firstSlices)) ? i->second : Links();
|
return (i != end(_firstSlices)) ? i->second : Links();
|
||||||
|
@ -147,28 +302,42 @@ auto InviteLinks::links(not_null<PeerData*> peer) const -> Links {
|
||||||
auto InviteLinks::parseSlice(
|
auto InviteLinks::parseSlice(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const MTPmessages_ExportedChatInvites &slice) const -> Links {
|
const MTPmessages_ExportedChatInvites &slice) const -> Links {
|
||||||
|
auto i = _firstSlices.find(peer);
|
||||||
|
const auto permanent = (i != end(_firstSlices))
|
||||||
|
? lookupPermanent(i->second)
|
||||||
|
: nullptr;
|
||||||
auto result = Links();
|
auto result = Links();
|
||||||
slice.match([&](const MTPDmessages_exportedChatInvites &data) {
|
slice.match([&](const MTPDmessages_exportedChatInvites &data) {
|
||||||
auto &owner = peer->session().data();
|
peer->session().data().processUsers(data.vusers());
|
||||||
owner.processUsers(data.vusers());
|
|
||||||
result.count = data.vcount().v;
|
result.count = data.vcount().v;
|
||||||
for (const auto &invite : data.vinvites().v) {
|
for (const auto &invite : data.vinvites().v) {
|
||||||
invite.match([&](const MTPDchatInviteExported &data) {
|
const auto link = parse(peer, invite);
|
||||||
result.links.push_back({
|
if (!permanent || link.link != permanent->link) {
|
||||||
.link = qs(data.vlink()),
|
result.links.push_back(link);
|
||||||
.admin = owner.user(data.vadmin_id().v),
|
}
|
||||||
.date = data.vdate().v,
|
|
||||||
.expireDate = data.vexpire_date().value_or_empty(),
|
|
||||||
.usageLimit = data.vusage_limit().value_or_empty(),
|
|
||||||
.usage = data.vusage().value_or_empty(),
|
|
||||||
.revoked = data.is_revoked(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto InviteLinks::parse(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
const MTPExportedChatInvite &invite) const -> Link {
|
||||||
|
return invite.match([&](const MTPDchatInviteExported &data) {
|
||||||
|
return Link{
|
||||||
|
.link = qs(data.vlink()),
|
||||||
|
.admin = peer->session().data().user(data.vadmin_id().v),
|
||||||
|
.date = data.vdate().v,
|
||||||
|
.expireDate = data.vexpire_date().value_or_empty(),
|
||||||
|
.usageLimit = data.vusage_limit().value_or_empty(),
|
||||||
|
.usage = data.vusage().value_or_empty(),
|
||||||
|
.permanent = data.is_permanent(),
|
||||||
|
.expired = data.is_expired(),
|
||||||
|
.revoked = data.is_revoked(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void InviteLinks::requestMoreLinks(
|
void InviteLinks::requestMoreLinks(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const QString &last,
|
const QString &last,
|
||||||
|
@ -180,7 +349,9 @@ void InviteLinks::requestMoreLinks(
|
||||||
MTP_string(last),
|
MTP_string(last),
|
||||||
MTP_int(kPerPage)
|
MTP_int(kPerPage)
|
||||||
)).done([=](const MTPmessages_ExportedChatInvites &result) {
|
)).done([=](const MTPmessages_ExportedChatInvites &result) {
|
||||||
done(parseSlice(peer, result));
|
auto slice = parseSlice(peer, result);
|
||||||
|
RemovePermanent(slice);
|
||||||
|
done(std::move(slice));
|
||||||
}).fail([=](const RPCError &error) {
|
}).fail([=](const RPCError &error) {
|
||||||
done(Links());
|
done(Links());
|
||||||
}).send();
|
}).send();
|
||||||
|
@ -188,16 +359,11 @@ void InviteLinks::requestMoreLinks(
|
||||||
|
|
||||||
void InviteLinks::editPermanentLink(
|
void InviteLinks::editPermanentLink(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const QString &from,
|
const QString &link) {
|
||||||
const QString &to) {
|
|
||||||
if (const auto chat = peer->asChat()) {
|
if (const auto chat = peer->asChat()) {
|
||||||
if (chat->inviteLink() == from) {
|
chat->setInviteLink(link);
|
||||||
chat->setInviteLink(to);
|
|
||||||
}
|
|
||||||
} else if (const auto channel = peer->asChannel()) {
|
} else if (const auto channel = peer->asChannel()) {
|
||||||
if (channel->inviteLink() == from) {
|
channel->setInviteLink(link);
|
||||||
channel->setInviteLink(to);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Unexpected("Peer in InviteLinks::editMainLink.");
|
Unexpected("Peer in InviteLinks::editMainLink.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ struct InviteLink {
|
||||||
TimeId expireDate = 0;
|
TimeId expireDate = 0;
|
||||||
int usageLimit = 0;
|
int usageLimit = 0;
|
||||||
int usage = 0;
|
int usage = 0;
|
||||||
|
bool permanent = false;
|
||||||
|
bool expired = false;
|
||||||
bool revoked = false;
|
bool revoked = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,14 +37,24 @@ public:
|
||||||
|
|
||||||
void create(
|
void create(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
|
Fn<void(Link)> done = nullptr,
|
||||||
TimeId expireDate = 0,
|
TimeId expireDate = 0,
|
||||||
int usageLimit = 0);
|
int usageLimit = 0);
|
||||||
void edit(
|
void edit(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const QString &link,
|
const QString &link,
|
||||||
TimeId expireDate,
|
TimeId expireDate,
|
||||||
int usageLimit);
|
int usageLimit,
|
||||||
void revoke(not_null<PeerData*> peer, const QString &link);
|
Fn<void(Link)> done = nullptr);
|
||||||
|
void revoke(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
const QString &link,
|
||||||
|
Fn<void(Link)> done = nullptr);
|
||||||
|
|
||||||
|
void setPermanent(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
const MTPExportedChatInvite &invite);
|
||||||
|
void clearPermanent(not_null<PeerData*> peer);
|
||||||
|
|
||||||
void requestLinks(not_null<PeerData*> peer);
|
void requestLinks(not_null<PeerData*> peer);
|
||||||
[[nodiscard]] Links links(not_null<PeerData*> peer) const;
|
[[nodiscard]] Links links(not_null<PeerData*> peer) const;
|
||||||
|
@ -64,21 +76,47 @@ private:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void editPermanentLink(
|
|
||||||
not_null<PeerData*> peer,
|
|
||||||
const QString &from,
|
|
||||||
const QString &to);
|
|
||||||
[[nodiscard]] Links parseSlice(
|
[[nodiscard]] Links parseSlice(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const MTPmessages_ExportedChatInvites &slice) const;
|
const MTPmessages_ExportedChatInvites &slice) const;
|
||||||
|
[[nodiscard]] Link parse(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
const MTPExportedChatInvite &invite) const;
|
||||||
|
[[nodiscard]] Link *lookupPermanent(not_null<PeerData*> peer);
|
||||||
|
[[nodiscard]] Link *lookupPermanent(Links &links);
|
||||||
|
[[nodiscard]] const Link *lookupPermanent(const Links &links) const;
|
||||||
|
Link prepend(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
const MTPExportedChatInvite &invite);
|
||||||
|
void notify(not_null<PeerData*> peer);
|
||||||
|
|
||||||
|
void editPermanentLink(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
const QString &link);
|
||||||
|
|
||||||
|
void performEdit(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
const QString &link,
|
||||||
|
Fn<void(Link)> done,
|
||||||
|
bool revoke,
|
||||||
|
TimeId expireDate = 0,
|
||||||
|
int usageLimit = 0);
|
||||||
|
void performCreate(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
Fn<void(Link)> done,
|
||||||
|
bool revokeLegacyPermanent,
|
||||||
|
TimeId expireDate = 0,
|
||||||
|
int usageLimit = 0);
|
||||||
|
|
||||||
const not_null<ApiWrap*> _api;
|
const not_null<ApiWrap*> _api;
|
||||||
|
|
||||||
base::flat_map<not_null<PeerData*>, Links> _firstSlices;
|
base::flat_map<not_null<PeerData*>, Links> _firstSlices;
|
||||||
base::flat_map<not_null<PeerData*>, mtpRequestId> _firstSliceRequests;
|
base::flat_map<not_null<PeerData*>, mtpRequestId> _firstSliceRequests;
|
||||||
|
|
||||||
base::flat_map<not_null<PeerData*>, mtpRequestId> _createRequests;
|
base::flat_map<
|
||||||
base::flat_map<EditKey, mtpRequestId> _editRequests;
|
not_null<PeerData*>,
|
||||||
|
std::vector<Fn<void(Link)>>> _createCallbacks;
|
||||||
|
base::flat_map<EditKey, std::vector<Fn<void(Link)>>> _editCallbacks;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -614,7 +614,9 @@ void GroupInfoBox::createGroup(
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupInfoBox::submit() {
|
void GroupInfoBox::submit() {
|
||||||
if (_creationRequestId) return;
|
if (_creationRequestId || _creatingInviteLink) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto title = TextUtilities::PrepareForSending(_title->getLastText());
|
auto title = TextUtilities::PrepareForSending(_title->getLastText());
|
||||||
auto description = _description
|
auto description = _description
|
||||||
|
@ -653,6 +655,8 @@ void GroupInfoBox::submit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupInfoBox::createChannel(const QString &title, const QString &description) {
|
void GroupInfoBox::createChannel(const QString &title, const QString &description) {
|
||||||
|
Expects(!_creationRequestId);
|
||||||
|
|
||||||
const auto flags = (_type == Type::Megagroup)
|
const auto flags = (_type == Type::Megagroup)
|
||||||
? MTPchannels_CreateChannel::Flag::f_megagroup
|
? MTPchannels_CreateChannel::Flag::f_megagroup
|
||||||
: MTPchannels_CreateChannel::Flag::f_broadcast;
|
: MTPchannels_CreateChannel::Flag::f_broadcast;
|
||||||
|
@ -692,29 +696,9 @@ void GroupInfoBox::createChannel(const QString &title, const QString &descriptio
|
||||||
channel,
|
channel,
|
||||||
std::move(image));
|
std::move(image));
|
||||||
}
|
}
|
||||||
|
using Flag = MTPmessages_ExportChatInvite::Flag;
|
||||||
_createdChannel = channel;
|
_createdChannel = channel;
|
||||||
_creationRequestId = _api.request(MTPmessages_ExportChatInvite(
|
checkInviteLink();
|
||||||
MTP_flags(0),
|
|
||||||
_createdChannel->input,
|
|
||||||
MTPint(), // expire_date
|
|
||||||
MTPint() // usage_limit
|
|
||||||
)).done([=](const MTPExportedChatInvite &result) {
|
|
||||||
_creationRequestId = 0;
|
|
||||||
if (result.type() == mtpc_chatInviteExported) {
|
|
||||||
auto link = qs(result.c_chatInviteExported().vlink());
|
|
||||||
_createdChannel->setInviteLink(link);
|
|
||||||
}
|
|
||||||
if (_channelDone) {
|
|
||||||
const auto callback = _channelDone;
|
|
||||||
const auto argument = _createdChannel;
|
|
||||||
closeBox();
|
|
||||||
callback(argument);
|
|
||||||
} else {
|
|
||||||
Ui::show(Box<SetupChannelBox>(
|
|
||||||
_navigation,
|
|
||||||
_createdChannel));
|
|
||||||
}
|
|
||||||
}).send();
|
|
||||||
};
|
};
|
||||||
if (!success) {
|
if (!success) {
|
||||||
LOG(("API Error: channel not found in updates (GroupInfoBox::creationDone)"));
|
LOG(("API Error: channel not found in updates (GroupInfoBox::creationDone)"));
|
||||||
|
@ -733,6 +717,32 @@ void GroupInfoBox::createChannel(const QString &title, const QString &descriptio
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GroupInfoBox::checkInviteLink() {
|
||||||
|
Expects(_createdChannel != nullptr);
|
||||||
|
|
||||||
|
if (!_createdChannel->inviteLink().isEmpty()) {
|
||||||
|
channelReady();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_creatingInviteLink = true;
|
||||||
|
_createdChannel->session().api().inviteLinks().create(
|
||||||
|
_createdChannel,
|
||||||
|
crl::guard(this, [=](auto&&) { channelReady(); }));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupInfoBox::channelReady() {
|
||||||
|
if (_channelDone) {
|
||||||
|
const auto callback = _channelDone;
|
||||||
|
const auto argument = _createdChannel;
|
||||||
|
closeBox();
|
||||||
|
callback(argument);
|
||||||
|
} else {
|
||||||
|
Ui::show(Box<SetupChannelBox>(
|
||||||
|
_navigation,
|
||||||
|
_createdChannel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GroupInfoBox::descriptionResized() {
|
void GroupInfoBox::descriptionResized() {
|
||||||
updateMaxHeight();
|
updateMaxHeight();
|
||||||
update();
|
update();
|
||||||
|
@ -834,7 +844,7 @@ void SetupChannelBox::prepare() {
|
||||||
|
|
||||||
_channel->session().changes().peerUpdates(
|
_channel->session().changes().peerUpdates(
|
||||||
_channel,
|
_channel,
|
||||||
Data::PeerUpdate::Flag::InviteLink
|
Data::PeerUpdate::Flag::InviteLinks
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
rtlupdate(_invitationLink);
|
rtlupdate(_invitationLink);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
|
@ -121,6 +121,8 @@ private:
|
||||||
void createGroup(not_null<PeerListBox*> selectUsersBox, const QString &title, const std::vector<not_null<PeerData*>> &users);
|
void createGroup(not_null<PeerListBox*> selectUsersBox, const QString &title, const std::vector<not_null<PeerData*>> &users);
|
||||||
void submitName();
|
void submitName();
|
||||||
void submit();
|
void submit();
|
||||||
|
void checkInviteLink();
|
||||||
|
void channelReady();
|
||||||
|
|
||||||
void descriptionResized();
|
void descriptionResized();
|
||||||
void updateMaxHeight();
|
void updateMaxHeight();
|
||||||
|
@ -138,6 +140,7 @@ private:
|
||||||
|
|
||||||
// group / channel creation
|
// group / channel creation
|
||||||
mtpRequestId _creationRequestId = 0;
|
mtpRequestId _creationRequestId = 0;
|
||||||
|
bool _creatingInviteLink = false;
|
||||||
ChannelData *_createdChannel = nullptr;
|
ChannelData *_createdChannel = nullptr;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -394,7 +394,7 @@ void MaxInviteBox::prepare() {
|
||||||
|
|
||||||
_channel->session().changes().peerUpdates(
|
_channel->session().changes().peerUpdates(
|
||||||
_channel,
|
_channel,
|
||||||
Data::PeerUpdate::Flag::InviteLink
|
Data::PeerUpdate::Flag::InviteLinks
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
rtlupdate(_invitationLink);
|
rtlupdate(_invitationLink);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
|
@ -331,7 +331,6 @@ private:
|
||||||
void showEditLinkedChatBox();
|
void showEditLinkedChatBox();
|
||||||
void fillPrivacyTypeButton();
|
void fillPrivacyTypeButton();
|
||||||
void fillLinkedChatButton();
|
void fillLinkedChatButton();
|
||||||
void fillInviteLinkButton();
|
|
||||||
void fillSignaturesButton();
|
void fillSignaturesButton();
|
||||||
void fillHistoryVisibilityButton();
|
void fillHistoryVisibilityButton();
|
||||||
void fillManageSection();
|
void fillManageSection();
|
||||||
|
@ -640,6 +639,8 @@ void Controller::refreshHistoryVisibility(anim::type animated) {
|
||||||
|
|
||||||
void Controller::showEditPeerTypeBox(
|
void Controller::showEditPeerTypeBox(
|
||||||
std::optional<rpl::producer<QString>> error) {
|
std::optional<rpl::producer<QString>> error) {
|
||||||
|
Expects(_privacySavedValue.has_value());
|
||||||
|
|
||||||
const auto boxCallback = crl::guard(this, [=](
|
const auto boxCallback = crl::guard(this, [=](
|
||||||
Privacy checked, QString publicLink) {
|
Privacy checked, QString publicLink) {
|
||||||
_privacyTypeUpdates.fire(std::move(checked));
|
_privacyTypeUpdates.fire(std::move(checked));
|
||||||
|
@ -652,7 +653,7 @@ void Controller::showEditPeerTypeBox(
|
||||||
_peer,
|
_peer,
|
||||||
_channelHasLocationOriginalValue,
|
_channelHasLocationOriginalValue,
|
||||||
boxCallback,
|
boxCallback,
|
||||||
_privacySavedValue,
|
*_privacySavedValue,
|
||||||
_usernameSavedValue,
|
_usernameSavedValue,
|
||||||
error),
|
error),
|
||||||
Ui::LayerOption::KeepOther);
|
Ui::LayerOption::KeepOther);
|
||||||
|
@ -798,22 +799,9 @@ void Controller::fillLinkedChatButton() {
|
||||||
_linkedChatUpdates.fire_copy(*_linkedChatSavedValue);
|
_linkedChatUpdates.fire_copy(*_linkedChatSavedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::fillInviteLinkButton() {
|
|
||||||
Expects(_controls.buttonsLayout != nullptr);
|
|
||||||
|
|
||||||
const auto buttonCallback = [=] {
|
|
||||||
Ui::show(Box<EditPeerTypeBox>(_peer), Ui::LayerOption::KeepOther);
|
|
||||||
};
|
|
||||||
|
|
||||||
AddButtonWithText(
|
|
||||||
_controls.buttonsLayout,
|
|
||||||
tr::lng_profile_invite_link_section(),
|
|
||||||
rpl::single(QString()), //Empty text.
|
|
||||||
buttonCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::fillSignaturesButton() {
|
void Controller::fillSignaturesButton() {
|
||||||
Expects(_controls.buttonsLayout != nullptr);
|
Expects(_controls.buttonsLayout != nullptr);
|
||||||
|
|
||||||
const auto channel = _peer->asChannel();
|
const auto channel = _peer->asChannel();
|
||||||
if (!channel) return;
|
if (!channel) return;
|
||||||
|
|
||||||
|
@ -964,8 +952,6 @@ void Controller::fillManageSection() {
|
||||||
|
|
||||||
if (canEditUsername) {
|
if (canEditUsername) {
|
||||||
fillPrivacyTypeButton();
|
fillPrivacyTypeButton();
|
||||||
} else if (canEditInviteLink) {
|
|
||||||
fillInviteLinkButton();
|
|
||||||
}
|
}
|
||||||
if (canViewOrEditLinkedChat) {
|
if (canViewOrEditLinkedChat) {
|
||||||
fillLinkedChatButton();
|
fillLinkedChatButton();
|
||||||
|
@ -1010,7 +996,7 @@ void Controller::fillManageSection() {
|
||||||
peer->session().api().inviteLinks().requestLinks(peer);
|
peer->session().api().inviteLinks().requestLinks(peer);
|
||||||
return peer->session().changes().peerUpdates(
|
return peer->session().changes().peerUpdates(
|
||||||
peer,
|
peer,
|
||||||
Data::PeerUpdate::Flag::InviteLink
|
Data::PeerUpdate::Flag::InviteLinks
|
||||||
) | rpl::map([=] {
|
) | rpl::map([=] {
|
||||||
return peer->session().api().inviteLinks().links(
|
return peer->session().api().inviteLinks().links(
|
||||||
peer).count;
|
peer).count;
|
||||||
|
@ -1018,7 +1004,7 @@ void Controller::fillManageSection() {
|
||||||
}) | rpl::flatten_latest(
|
}) | rpl::flatten_latest(
|
||||||
) | ToPositiveNumberString(),
|
) | ToPositiveNumberString(),
|
||||||
[=] { ShowEditInviteLinks(_navigation, _peer); },
|
[=] { ShowEditInviteLinks(_navigation, _peer); },
|
||||||
st::infoIconPermissions);
|
st::infoIconInviteLinks);
|
||||||
}
|
}
|
||||||
if (canViewAdmins) {
|
if (canViewAdmins) {
|
||||||
AddButtonWithCount(
|
AddButtonWithCount(
|
||||||
|
|
|
@ -58,7 +58,7 @@ public:
|
||||||
not_null<Ui::VerticalLayout*> container,
|
not_null<Ui::VerticalLayout*> container,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
bool useLocationPhrases,
|
bool useLocationPhrases,
|
||||||
std::optional<Privacy> privacySavedValue,
|
Privacy privacySavedValue,
|
||||||
std::optional<QString> usernameSavedValue);
|
std::optional<QString> usernameSavedValue);
|
||||||
|
|
||||||
void createContent();
|
void createContent();
|
||||||
|
@ -66,17 +66,11 @@ public:
|
||||||
void setFocusUsername();
|
void setFocusUsername();
|
||||||
|
|
||||||
rpl::producer<QString> getTitle() {
|
rpl::producer<QString> getTitle() {
|
||||||
return _isInviteLink
|
return _isGroup
|
||||||
? tr::lng_profile_invite_link_section()
|
|
||||||
: _isGroup
|
|
||||||
? tr::lng_manage_peer_group_type()
|
? tr::lng_manage_peer_group_type()
|
||||||
: tr::lng_manage_peer_channel_type();
|
: tr::lng_manage_peer_channel_type();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isInviteLink() {
|
|
||||||
return _isInviteLink;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isAllowSave() {
|
bool isAllowSave() {
|
||||||
return _isAllowSave;
|
return _isAllowSave;
|
||||||
}
|
}
|
||||||
|
@ -98,17 +92,14 @@ private:
|
||||||
base::unique_qptr<Ui::FlatLabel> usernameResult;
|
base::unique_qptr<Ui::FlatLabel> usernameResult;
|
||||||
const style::FlatLabel *usernameResultStyle = nullptr;
|
const style::FlatLabel *usernameResultStyle = nullptr;
|
||||||
|
|
||||||
Ui::SlideWrap<Ui::RpWidget> *createInviteLinkWrap = nullptr;
|
Ui::SlideWrap<Ui::RpWidget> *inviteLinkWrap = nullptr;
|
||||||
Ui::SlideWrap<Ui::RpWidget> *editInviteLinkWrap = nullptr;
|
|
||||||
Ui::FlatLabel *inviteLink = nullptr;
|
Ui::FlatLabel *inviteLink = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
Controls _controls;
|
Controls _controls;
|
||||||
|
|
||||||
object_ptr<Ui::RpWidget> createPrivaciesEdit();
|
|
||||||
object_ptr<Ui::RpWidget> createUsernameEdit();
|
object_ptr<Ui::RpWidget> createUsernameEdit();
|
||||||
object_ptr<Ui::RpWidget> createInviteLinkCreate();
|
object_ptr<Ui::RpWidget> createInviteLinkBlock();
|
||||||
object_ptr<Ui::RpWidget> createInviteLinkEdit();
|
|
||||||
|
|
||||||
void observeInviteLink();
|
void observeInviteLink();
|
||||||
|
|
||||||
|
@ -124,8 +115,7 @@ private:
|
||||||
not_null<const style::FlatLabel*> st);
|
not_null<const style::FlatLabel*> st);
|
||||||
|
|
||||||
bool canEditInviteLink() const;
|
bool canEditInviteLink() const;
|
||||||
void refreshEditInviteLink();
|
void refreshInviteLinkBlock();
|
||||||
void refreshCreateInviteLink();
|
|
||||||
void createInviteLink();
|
void createInviteLink();
|
||||||
void revokeInviteLink(const QString &link);
|
void revokeInviteLink(const QString &link);
|
||||||
|
|
||||||
|
@ -143,12 +133,11 @@ private:
|
||||||
|
|
||||||
not_null<PeerData*> _peer;
|
not_null<PeerData*> _peer;
|
||||||
MTP::Sender _api;
|
MTP::Sender _api;
|
||||||
std::optional<Privacy> _privacySavedValue;
|
Privacy _privacySavedValue = Privacy();
|
||||||
std::optional<QString> _usernameSavedValue;
|
std::optional<QString> _usernameSavedValue;
|
||||||
|
|
||||||
bool _useLocationPhrases = false;
|
bool _useLocationPhrases = false;
|
||||||
bool _isGroup = false;
|
bool _isGroup = false;
|
||||||
bool _isInviteLink = false;
|
|
||||||
bool _isAllowSave = false;
|
bool _isAllowSave = false;
|
||||||
|
|
||||||
base::unique_qptr<Ui::VerticalLayout> _wrap;
|
base::unique_qptr<Ui::VerticalLayout> _wrap;
|
||||||
|
@ -165,7 +154,7 @@ Controller::Controller(
|
||||||
not_null<Ui::VerticalLayout*> container,
|
not_null<Ui::VerticalLayout*> container,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
bool useLocationPhrases,
|
bool useLocationPhrases,
|
||||||
std::optional<Privacy> privacySavedValue,
|
Privacy privacySavedValue,
|
||||||
std::optional<QString> usernameSavedValue)
|
std::optional<QString> usernameSavedValue)
|
||||||
: _peer(peer)
|
: _peer(peer)
|
||||||
, _api(&_peer->session().mtp())
|
, _api(&_peer->session().mtp())
|
||||||
|
@ -173,8 +162,6 @@ Controller::Controller(
|
||||||
, _usernameSavedValue(usernameSavedValue)
|
, _usernameSavedValue(usernameSavedValue)
|
||||||
, _useLocationPhrases(useLocationPhrases)
|
, _useLocationPhrases(useLocationPhrases)
|
||||||
, _isGroup(_peer->isChat() || _peer->isMegagroup())
|
, _isGroup(_peer->isChat() || _peer->isMegagroup())
|
||||||
, _isInviteLink(!_privacySavedValue.has_value()
|
|
||||||
&& !_usernameSavedValue.has_value())
|
|
||||||
, _isAllowSave(!_usernameSavedValue.value_or(QString()).isEmpty())
|
, _isAllowSave(!_usernameSavedValue.value_or(QString()).isEmpty())
|
||||||
, _wrap(container)
|
, _wrap(container)
|
||||||
, _checkUsernameTimer([=] { checkUsernameAvailability(); }) {
|
, _checkUsernameTimer([=] { checkUsernameAvailability(); }) {
|
||||||
|
@ -184,18 +171,11 @@ Controller::Controller(
|
||||||
void Controller::createContent() {
|
void Controller::createContent() {
|
||||||
_controls = Controls();
|
_controls = Controls();
|
||||||
|
|
||||||
if (_isInviteLink) {
|
|
||||||
_wrap->add(createInviteLinkCreate());
|
|
||||||
_wrap->add(createInviteLinkEdit());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fillPrivaciesButtons(_wrap, _privacySavedValue);
|
fillPrivaciesButtons(_wrap, _privacySavedValue);
|
||||||
// Skip.
|
// Skip.
|
||||||
_wrap->add(object_ptr<Ui::BoxContentDivider>(_wrap));
|
_wrap->add(object_ptr<Ui::BoxContentDivider>(_wrap));
|
||||||
//
|
//
|
||||||
_wrap->add(createInviteLinkCreate());
|
_wrap->add(createInviteLinkBlock());
|
||||||
_wrap->add(createInviteLinkEdit());
|
|
||||||
_wrap->add(createUsernameEdit());
|
_wrap->add(createUsernameEdit());
|
||||||
|
|
||||||
if (_controls.privacy->value() == Privacy::NoUsername) {
|
if (_controls.privacy->value() == Privacy::NoUsername) {
|
||||||
|
@ -378,16 +358,14 @@ void Controller::privacyChanged(Privacy value) {
|
||||||
// Otherwise box will change own Y position.
|
// Otherwise box will change own Y position.
|
||||||
|
|
||||||
if (value == Privacy::HasUsername) {
|
if (value == Privacy::HasUsername) {
|
||||||
refreshCreateInviteLink();
|
refreshInviteLinkBlock();
|
||||||
refreshEditInviteLink();
|
|
||||||
toggleEditUsername();
|
toggleEditUsername();
|
||||||
|
|
||||||
_controls.usernameResult = nullptr;
|
_controls.usernameResult = nullptr;
|
||||||
checkUsernameAvailability();
|
checkUsernameAvailability();
|
||||||
} else {
|
} else {
|
||||||
toggleEditUsername();
|
toggleEditUsername();
|
||||||
refreshCreateInviteLink();
|
refreshInviteLinkBlock();
|
||||||
refreshEditInviteLink();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (value == Privacy::HasUsername) {
|
if (value == Privacy::HasUsername) {
|
||||||
|
@ -562,29 +540,26 @@ void Controller::revokeInviteLink(const QString &link) {
|
||||||
|
|
||||||
bool Controller::canEditInviteLink() const {
|
bool Controller::canEditInviteLink() const {
|
||||||
if (const auto channel = _peer->asChannel()) {
|
if (const auto channel = _peer->asChannel()) {
|
||||||
return channel->amCreator()
|
return channel->canHaveInviteLink();
|
||||||
|| (channel->adminRights() & ChatAdminRight::f_invite_users);
|
|
||||||
} else if (const auto chat = _peer->asChat()) {
|
} else if (const auto chat = _peer->asChat()) {
|
||||||
return chat->amCreator()
|
return chat->canHaveInviteLink();
|
||||||
|| (chat->adminRights() & ChatAdminRight::f_invite_users);
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::observeInviteLink() {
|
void Controller::observeInviteLink() {
|
||||||
if (!_controls.editInviteLinkWrap) {
|
if (!_controls.inviteLinkWrap) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_peer->session().changes().peerFlagsValue(
|
_peer->session().changes().peerFlagsValue(
|
||||||
_peer,
|
_peer,
|
||||||
Data::PeerUpdate::Flag::InviteLink
|
Data::PeerUpdate::Flag::InviteLinks
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
refreshCreateInviteLink();
|
refreshInviteLinkBlock();
|
||||||
refreshEditInviteLink();
|
}, _controls.inviteLinkWrap->lifetime());
|
||||||
}, _controls.editInviteLinkWrap->lifetime());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object_ptr<Ui::RpWidget> Controller::createInviteLinkEdit() {
|
object_ptr<Ui::RpWidget> Controller::createInviteLinkBlock() {
|
||||||
Expects(_wrap != nullptr);
|
Expects(_wrap != nullptr);
|
||||||
|
|
||||||
if (!canEditInviteLink()) {
|
if (!canEditInviteLink()) {
|
||||||
|
@ -595,18 +570,16 @@ object_ptr<Ui::RpWidget> Controller::createInviteLinkEdit() {
|
||||||
_wrap,
|
_wrap,
|
||||||
object_ptr<Ui::VerticalLayout>(_wrap),
|
object_ptr<Ui::VerticalLayout>(_wrap),
|
||||||
st::editPeerInvitesMargins);
|
st::editPeerInvitesMargins);
|
||||||
_controls.editInviteLinkWrap = result.data();
|
_controls.inviteLinkWrap = result.data();
|
||||||
|
|
||||||
const auto container = result->entity();
|
const auto container = result->entity();
|
||||||
if (!_isInviteLink) {
|
container->add(object_ptr<Ui::FlatLabel>(
|
||||||
container->add(object_ptr<Ui::FlatLabel>(
|
container,
|
||||||
container,
|
tr::lng_profile_invite_link_section(),
|
||||||
tr::lng_profile_invite_link_section(),
|
st::editPeerSectionLabel));
|
||||||
st::editPeerSectionLabel));
|
container->add(object_ptr<Ui::FixedHeightWidget>(
|
||||||
container->add(object_ptr<Ui::FixedHeightWidget>(
|
container,
|
||||||
container,
|
st::editPeerInviteLinkBoxBottomSkip));
|
||||||
st::editPeerInviteLinkBoxBottomSkip));
|
|
||||||
}
|
|
||||||
|
|
||||||
_controls.inviteLink = container->add(object_ptr<Ui::FlatLabel>(
|
_controls.inviteLink = container->add(object_ptr<Ui::FlatLabel>(
|
||||||
container,
|
container,
|
||||||
|
@ -634,7 +607,7 @@ object_ptr<Ui::RpWidget> Controller::createInviteLinkEdit() {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::refreshEditInviteLink() {
|
void Controller::refreshInviteLinkBlock() {
|
||||||
const auto link = inviteLinkText();
|
const auto link = inviteLinkText();
|
||||||
auto text = TextWithEntities();
|
auto text = TextWithEntities();
|
||||||
if (!link.isEmpty()) {
|
if (!link.isEmpty()) {
|
||||||
|
@ -652,74 +625,26 @@ void Controller::refreshEditInviteLink() {
|
||||||
_controls.inviteLink->setMarkedText(text);
|
_controls.inviteLink->setMarkedText(text);
|
||||||
|
|
||||||
// Hack to expand FlatLabel width to naturalWidth again.
|
// Hack to expand FlatLabel width to naturalWidth again.
|
||||||
_controls.editInviteLinkWrap->resizeToWidth(st::boxWideWidth);
|
_controls.inviteLinkWrap->resizeToWidth(st::boxWideWidth);
|
||||||
|
|
||||||
_controls.editInviteLinkWrap->toggle(
|
_controls.inviteLinkWrap->toggle(
|
||||||
inviteLinkShown() && !link.isEmpty(),
|
inviteLinkShown() && !link.isEmpty(),
|
||||||
anim::type::instant);
|
anim::type::instant);
|
||||||
}
|
}
|
||||||
|
|
||||||
object_ptr<Ui::RpWidget> Controller::createInviteLinkCreate() {
|
|
||||||
Expects(_wrap != nullptr);
|
|
||||||
|
|
||||||
if (!canEditInviteLink()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
|
||||||
_wrap,
|
|
||||||
object_ptr<Ui::VerticalLayout>(_wrap),
|
|
||||||
st::editPeerInvitesMargins);
|
|
||||||
const auto container = result->entity();
|
|
||||||
|
|
||||||
if (!_isInviteLink) {
|
|
||||||
container->add(object_ptr<Ui::FlatLabel>(
|
|
||||||
container,
|
|
||||||
tr::lng_profile_invite_link_section(),
|
|
||||||
st::editPeerSectionLabel));
|
|
||||||
container->add(object_ptr<Ui::FixedHeightWidget>(
|
|
||||||
container,
|
|
||||||
st::editPeerInviteLinkSkip));
|
|
||||||
}
|
|
||||||
|
|
||||||
container->add(object_ptr<Ui::LinkButton>(
|
|
||||||
_wrap,
|
|
||||||
tr::lng_group_invite_create(tr::now),
|
|
||||||
st::editPeerInviteLinkButton)
|
|
||||||
)->addClickHandler([this] {
|
|
||||||
createInviteLink();
|
|
||||||
});
|
|
||||||
_controls.createInviteLinkWrap = result.data();
|
|
||||||
|
|
||||||
observeInviteLink();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::refreshCreateInviteLink() {
|
|
||||||
_controls.createInviteLinkWrap->toggle(
|
|
||||||
inviteLinkShown() && inviteLinkText().isEmpty(),
|
|
||||||
anim::type::instant);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Controller::inviteLinkShown() {
|
bool Controller::inviteLinkShown() {
|
||||||
return !_controls.privacy
|
return !_controls.privacy
|
||||||
|| (_controls.privacy->value() == Privacy::NoUsername)
|
|| (_controls.privacy->value() == Privacy::NoUsername);
|
||||||
|| _isInviteLink;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
EditPeerTypeBox::EditPeerTypeBox(QWidget*, not_null<PeerData*> peer)
|
|
||||||
: EditPeerTypeBox(nullptr, peer, false, {}, {}, {}, {}) {
|
|
||||||
}
|
|
||||||
|
|
||||||
EditPeerTypeBox::EditPeerTypeBox(
|
EditPeerTypeBox::EditPeerTypeBox(
|
||||||
QWidget*,
|
QWidget*,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
bool useLocationPhrases,
|
bool useLocationPhrases,
|
||||||
std::optional<FnMut<void(Privacy, QString)>> savedCallback,
|
std::optional<FnMut<void(Privacy, QString)>> savedCallback,
|
||||||
std::optional<Privacy> privacySaved,
|
Privacy privacySaved,
|
||||||
std::optional<QString> usernameSaved,
|
std::optional<QString> usernameSaved,
|
||||||
std::optional<rpl::producer<QString>> usernameError)
|
std::optional<rpl::producer<QString>> usernameError)
|
||||||
: _peer(peer)
|
: _peer(peer)
|
||||||
|
@ -760,7 +685,7 @@ void EditPeerTypeBox::prepare() {
|
||||||
|
|
||||||
setTitle(controller->getTitle());
|
setTitle(controller->getTitle());
|
||||||
|
|
||||||
if (!controller->isInviteLink() && _savedCallback.has_value()) {
|
if (_savedCallback.has_value()) {
|
||||||
addButton(tr::lng_settings_save(), [=] {
|
addButton(tr::lng_settings_save(), [=] {
|
||||||
const auto v = controller->getPrivacy();
|
const auto v = controller->getPrivacy();
|
||||||
if (!controller->isAllowSave() && (v == Privacy::HasUsername)) {
|
if (!controller->isAllowSave() && (v == Privacy::HasUsername)) {
|
||||||
|
@ -772,13 +697,11 @@ void EditPeerTypeBox::prepare() {
|
||||||
local(v,
|
local(v,
|
||||||
(v == Privacy::HasUsername)
|
(v == Privacy::HasUsername)
|
||||||
? controller->getUsernameInput()
|
? controller->getUsernameInput()
|
||||||
: QString()); // We dont need username with private type.
|
: QString()); // We don't need username with private type.
|
||||||
closeBox();
|
closeBox();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
addButton(
|
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||||
controller->isInviteLink() ? tr::lng_close() : tr::lng_cancel(),
|
|
||||||
[=] { closeBox(); });
|
|
||||||
|
|
||||||
setDimensionsToContent(st::boxWideWidth, content);
|
setDimensionsToContent(st::boxWideWidth, content);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,15 +32,12 @@ enum class UsernameState {
|
||||||
|
|
||||||
class EditPeerTypeBox : public Ui::BoxContent {
|
class EditPeerTypeBox : public Ui::BoxContent {
|
||||||
public:
|
public:
|
||||||
// Edit just the invite link.
|
|
||||||
EditPeerTypeBox(QWidget*, not_null<PeerData*> peer);
|
|
||||||
|
|
||||||
EditPeerTypeBox(
|
EditPeerTypeBox(
|
||||||
QWidget*,
|
QWidget*,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
bool useLocationPhrases,
|
bool useLocationPhrases,
|
||||||
std::optional<FnMut<void(Privacy, QString)>> savedCallback,
|
std::optional<FnMut<void(Privacy, QString)>> savedCallback,
|
||||||
std::optional<Privacy> privacySaved,
|
Privacy privacySaved,
|
||||||
std::optional<QString> usernameSaved,
|
std::optional<QString> usernameSaved,
|
||||||
std::optional<rpl::producer<QString>> usernameError = {});
|
std::optional<rpl::producer<QString>> usernameError = {});
|
||||||
|
|
||||||
|
@ -53,7 +50,7 @@ private:
|
||||||
bool _useLocationPhrases = false;
|
bool _useLocationPhrases = false;
|
||||||
std::optional<FnMut<void(Privacy, QString)>> _savedCallback;
|
std::optional<FnMut<void(Privacy, QString)>> _savedCallback;
|
||||||
|
|
||||||
std::optional<Privacy> _privacySavedValue;
|
Privacy _privacySavedValue = Privacy();
|
||||||
std::optional<QString> _usernameSavedValue;
|
std::optional<QString> _usernameSavedValue;
|
||||||
std::optional<rpl::producer<QString>> _usernameError;
|
std::optional<rpl::producer<QString>> _usernameError;
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_group_call.h"
|
#include "data/data_group_call.h"
|
||||||
|
#include "data/data_changes.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "boxes/single_choice_box.h"
|
#include "boxes/single_choice_box.h"
|
||||||
#include "webrtc/webrtc_audio_input_tester.h"
|
#include "webrtc/webrtc_audio_input_tester.h"
|
||||||
|
@ -32,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "settings/settings_calls.h"
|
#include "settings/settings_calls.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "api/api_invite_links.h"
|
||||||
#include "styles/style_layers.h"
|
#include "styles/style_layers.h"
|
||||||
#include "styles/style_calls.h"
|
#include "styles/style_calls.h"
|
||||||
#include "styles/style_settings.h"
|
#include "styles/style_settings.h"
|
||||||
|
@ -441,23 +443,9 @@ void GroupCallSettingsBox(
|
||||||
)->addClickHandler([=] {
|
)->addClickHandler([=] {
|
||||||
if (!copyLink() && !state->generatingLink) {
|
if (!copyLink() && !state->generatingLink) {
|
||||||
state->generatingLink = true;
|
state->generatingLink = true;
|
||||||
peer->session().api().request(MTPmessages_ExportChatInvite(
|
peer->session().api().inviteLinks().create(
|
||||||
MTP_flags(0),
|
peer,
|
||||||
peer->input,
|
crl::guard(layout, [=](auto&&) { copyLink(); }));
|
||||||
MTPint(), // expire_date
|
|
||||||
MTPint() // usage_limit
|
|
||||||
)).done([=](const MTPExportedChatInvite &result) {
|
|
||||||
if (result.type() == mtpc_chatInviteExported) {
|
|
||||||
const auto link = qs(
|
|
||||||
result.c_chatInviteExported().vlink());
|
|
||||||
if (const auto chat = peer->asChat()) {
|
|
||||||
chat->setInviteLink(link);
|
|
||||||
} else if (const auto channel = peer->asChannel()) {
|
|
||||||
channel->setInviteLink(link);
|
|
||||||
}
|
|
||||||
copyLink();
|
|
||||||
}
|
|
||||||
}).send();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ struct PeerUpdate {
|
||||||
IsBot = (1 << 19),
|
IsBot = (1 << 19),
|
||||||
|
|
||||||
// For chats and channels
|
// For chats and channels
|
||||||
InviteLink = (1 << 20),
|
InviteLinks = (1 << 20),
|
||||||
Members = (1 << 21),
|
Members = (1 << 21),
|
||||||
Admins = (1 << 22),
|
Admins = (1 << 22),
|
||||||
BannedUsers = (1 << 23),
|
BannedUsers = (1 << 23),
|
||||||
|
|
|
@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "api/api_chat_invite.h"
|
#include "api/api_chat_invite.h"
|
||||||
|
#include "api/api_invite_links.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -98,10 +99,7 @@ void ChannelData::setAccessHash(uint64 accessHash) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChannelData::setInviteLink(const QString &newInviteLink) {
|
void ChannelData::setInviteLink(const QString &newInviteLink) {
|
||||||
if (newInviteLink != _inviteLink) {
|
_inviteLink = newInviteLink;
|
||||||
_inviteLink = newInviteLink;
|
|
||||||
session().changes().peerUpdated(this, UpdateFlag::InviteLink);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChannelData::canHaveInviteLink() const {
|
bool ChannelData::canHaveInviteLink() const {
|
||||||
|
@ -778,11 +776,11 @@ void ApplyChannelUpdate(
|
||||||
next->v - channel->slowmodeSeconds());
|
next->v - channel->slowmodeSeconds());
|
||||||
}
|
}
|
||||||
if (const auto invite = update.vexported_invite()) {
|
if (const auto invite = update.vexported_invite()) {
|
||||||
invite->match([&](const MTPDchatInviteExported &data) {
|
channel->session().api().inviteLinks().setPermanent(
|
||||||
channel->setInviteLink(qs(data.vlink()));
|
channel,
|
||||||
});
|
*invite);
|
||||||
} else {
|
} else {
|
||||||
channel->setInviteLink(QString());
|
channel->session().api().inviteLinks().clearPermanent(channel);
|
||||||
}
|
}
|
||||||
if (const auto location = update.vlocation()) {
|
if (const auto location = update.vlocation()) {
|
||||||
channel->setLocation(*location);
|
channel->setLocation(*location);
|
||||||
|
|
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "api/api_invite_links.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -127,10 +128,7 @@ void ChatData::invalidateParticipants() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatData::setInviteLink(const QString &newInviteLink) {
|
void ChatData::setInviteLink(const QString &newInviteLink) {
|
||||||
if (newInviteLink != _inviteLink) {
|
_inviteLink = newInviteLink;
|
||||||
_inviteLink = newInviteLink;
|
|
||||||
session().changes().peerUpdated(this, UpdateFlag::InviteLink);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChatData::canHaveInviteLink() const {
|
bool ChatData::canHaveInviteLink() const {
|
||||||
|
@ -389,11 +387,9 @@ void ApplyChatUpdate(not_null<ChatData*> chat, const MTPDchatFull &update) {
|
||||||
chat->setUserpicPhoto(MTP_photoEmpty(MTP_long(0)));
|
chat->setUserpicPhoto(MTP_photoEmpty(MTP_long(0)));
|
||||||
}
|
}
|
||||||
if (const auto invite = update.vexported_invite()) {
|
if (const auto invite = update.vexported_invite()) {
|
||||||
invite->match([&](const MTPDchatInviteExported &data) {
|
chat->session().api().inviteLinks().setPermanent(chat, *invite);
|
||||||
chat->setInviteLink(qs(data.vlink()));
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
chat->setInviteLink(QString());
|
chat->session().api().inviteLinks().clearPermanent(chat);
|
||||||
}
|
}
|
||||||
if (const auto pinned = update.vpinned_msg_id()) {
|
if (const auto pinned = update.vpinned_msg_id()) {
|
||||||
SetTopPinnedMessageId(chat, pinned->v);
|
SetTopPinnedMessageId(chat, pinned->v);
|
||||||
|
|
|
@ -348,7 +348,7 @@ infoProfileSeparatorPadding: margins(
|
||||||
|
|
||||||
infoIconFg: menuIconFg;
|
infoIconFg: menuIconFg;
|
||||||
infoIconInformation: icon {{ "info_information", infoIconFg }};
|
infoIconInformation: icon {{ "info_information", infoIconFg }};
|
||||||
infoIconMembers: icon {{ "info_members", infoIconFg }};
|
infoIconMembers: icon {{ "info/edit/group_manage_members", infoIconFg, point(-2px, 0px) }};
|
||||||
infoIconNotifications: icon {{ "info_notifications", infoIconFg }};
|
infoIconNotifications: icon {{ "info_notifications", infoIconFg }};
|
||||||
infoIconActions: icon {{ "info_actions", infoIconFg }};
|
infoIconActions: icon {{ "info_actions", infoIconFg }};
|
||||||
//infoIconFeed: icon {{ "info_feed", infoIconFg }};
|
//infoIconFeed: icon {{ "info_feed", infoIconFg }};
|
||||||
|
@ -360,10 +360,11 @@ infoIconMediaLink: icon {{ "info_media_link", infoIconFg }};
|
||||||
infoIconMediaGroup: icon {{ "info_common_groups", infoIconFg }};
|
infoIconMediaGroup: icon {{ "info_common_groups", infoIconFg }};
|
||||||
infoIconMediaVoice: icon {{ "info_media_voice", infoIconFg }};
|
infoIconMediaVoice: icon {{ "info_media_voice", infoIconFg }};
|
||||||
infoIconMediaRound: icon {{ "info_media_round", infoIconFg }};
|
infoIconMediaRound: icon {{ "info_media_round", infoIconFg }};
|
||||||
infoIconRecentActions: icon {{ "info_recent_actions", infoIconFg, point(-2px, 0px) }};
|
infoIconRecentActions: icon {{ "info/edit/group_manage_actions", infoIconFg, point(-2px, -1px) }};
|
||||||
infoIconAdministrators: icon {{ "info_administrators", infoIconFg, point(-2px, -1px) }};
|
infoIconAdministrators: icon {{ "info/edit/group_manage_admins", infoIconFg, point(-3px, 0px) }};
|
||||||
infoIconBlacklist: icon {{ "info_blacklist", infoIconFg, point(-2px, -2px) }};
|
infoIconBlacklist: icon {{ "info_blacklist", infoIconFg, point(-2px, -2px) }};
|
||||||
infoIconPermissions: icon {{ "info_permissions", infoIconFg }};
|
infoIconPermissions: icon {{ "info/edit/group_manage_permissions", infoIconFg, point(0px, -2px) }};
|
||||||
|
infoIconInviteLinks: icon {{ "info/edit/group_manage_links", infoIconFg, point(-2px, 0px) }};
|
||||||
infoInformationIconPosition: point(25px, 12px);
|
infoInformationIconPosition: point(25px, 12px);
|
||||||
infoNotificationsIconPosition: point(20px, 5px);
|
infoNotificationsIconPosition: point(20px, 5px);
|
||||||
infoSharedMediaIconPosition: point(20px, 24px);
|
infoSharedMediaIconPosition: point(20px, 24px);
|
||||||
|
|