Implement new permanent invite link management.

This commit is contained in:
John Preston 2021-01-14 17:13:43 +04:00
parent 02ad5f2772
commit e5320b4b4e
29 changed files with 385 additions and 280 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 933 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -934,7 +934,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_peer_permissions" = "Permissions";
"lng_manage_peer_invite_links" = "Invite links";
"lng_manage_peer_group_type" = "Group type";
"lng_manage_peer_channel_type" = "Channel type";
"lng_manage_peer_link_type" = "Link type";

View File

@ -21,6 +21,23 @@ namespace {
constexpr auto kFirstPage = 10;
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
InviteLinks::InviteLinks(not_null<ApiWrap*> api) : _api(api) {
@ -28,93 +45,167 @@ InviteLinks::InviteLinks(not_null<ApiWrap*> api) : _api(api) {
void InviteLinks::create(
not_null<PeerData*> peer,
Fn<void(Link)> done,
TimeId expireDate,
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;
}
auto &callbacks = _createCallbacks[peer];
if (done) {
callbacks.push_back(std::move(done));
}
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))
| (usageLimit ? Flag::f_usage_limit : Flag(0))),
peer->input,
MTP_int(expireDate),
MTP_int(usageLimit)
)).done([=](const MTPExportedChatInvite &result) {
_createRequests.erase(peer);
const auto link = (result.type() == mtpc_chatInviteExported)
? qs(result.c_chatInviteExported().vlink())
: QString();
if (!expireDate && !usageLimit) {
editPermanentLink(peer, QString(), link);
const auto callbacks = _createCallbacks.take(peer);
const auto link = prepend(peer, result);
if (callbacks) {
for (const auto &callback : *callbacks) {
callback(link);
}
}
}).fail([=](const RPCError &error) {
_createRequests.erase(peer);
_createCallbacks.erase(peer);
}).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(
not_null<PeerData*> peer,
const QString &link,
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) {
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;
}
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;
const auto requestId = _api->request(MTPmessages_EditExportedChatInvite(
MTP_flags((expireDate ? Flag::f_expire_date : Flag(0))
| (usageLimit ? Flag::f_usage_limit : Flag(0))),
MTP_flags((revoke ? Flag::f_revoked : Flag(0))
| ((!revoke && expireDate) ? Flag::f_expire_date : Flag(0))
| ((!revoke && usageLimit) ? Flag::f_usage_limit : Flag(0))),
peer->input,
MTP_string(link),
MTP_int(expireDate),
MTP_int(usageLimit)
)).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) {
_api->session().data().processUsers(data.vusers());
const auto &invite = data.vinvite();
const auto link = (invite.type() == mtpc_chatInviteExported)
? qs(invite.c_chatInviteExported().vlink())
: QString();
// #TODO links
const auto link = parse(peer, data.vinvite());
auto i = _firstSlices.find(peer);
if (i != end(_firstSlices)) {
const auto j = ranges::find(
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) {
_editRequests.erase(key);
_editCallbacks.erase(key);
}).send();
_editRequests.emplace(key, requestId);
}
void InviteLinks::revoke(not_null<PeerData*> peer, const QString &link) {
const auto key = EditKey{ peer, link };
if (_editRequests.contains(key)) {
return;
}
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::revoke(
not_null<PeerData*> peer,
const QString &link,
Fn<void(Link)> done) {
performEdit(peer, link, std::move(done), true);
}
void InviteLinks::requestLinks(not_null<PeerData*> peer) {
@ -129,16 +220,80 @@ void InviteLinks::requestLinks(not_null<PeerData*> peer) {
MTP_int(kFirstPage)
)).done([=](const MTPmessages_ExportedChatInvites &result) {
_firstSliceRequests.remove(peer);
_firstSlices.emplace_or_assign(peer, parseSlice(peer, result));
peer->session().changes().peerUpdated(
peer,
Data::PeerUpdate::Flag::InviteLink);
auto slice = parseSlice(peer, result);
auto i = _firstSlices.find(peer);
const auto permanent = (i != end(_firstSlices))
? 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) {
_firstSliceRequests.remove(peer);
}).send();
_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 {
const auto i = _firstSlices.find(peer);
return (i != end(_firstSlices)) ? i->second : Links();
@ -147,28 +302,42 @@ auto InviteLinks::links(not_null<PeerData*> peer) const -> Links {
auto InviteLinks::parseSlice(
not_null<PeerData*> peer,
const MTPmessages_ExportedChatInvites &slice) const -> Links {
auto i = _firstSlices.find(peer);
const auto permanent = (i != end(_firstSlices))
? lookupPermanent(i->second)
: nullptr;
auto result = Links();
slice.match([&](const MTPDmessages_exportedChatInvites &data) {
auto &owner = peer->session().data();
owner.processUsers(data.vusers());
peer->session().data().processUsers(data.vusers());
result.count = data.vcount().v;
for (const auto &invite : data.vinvites().v) {
invite.match([&](const MTPDchatInviteExported &data) {
result.links.push_back({
.link = qs(data.vlink()),
.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(),
});
});
const auto link = parse(peer, invite);
if (!permanent || link.link != permanent->link) {
result.links.push_back(link);
}
}
});
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(
not_null<PeerData*> peer,
const QString &last,
@ -180,7 +349,9 @@ void InviteLinks::requestMoreLinks(
MTP_string(last),
MTP_int(kPerPage)
)).done([=](const MTPmessages_ExportedChatInvites &result) {
done(parseSlice(peer, result));
auto slice = parseSlice(peer, result);
RemovePermanent(slice);
done(std::move(slice));
}).fail([=](const RPCError &error) {
done(Links());
}).send();
@ -188,16 +359,11 @@ void InviteLinks::requestMoreLinks(
void InviteLinks::editPermanentLink(
not_null<PeerData*> peer,
const QString &from,
const QString &to) {
const QString &link) {
if (const auto chat = peer->asChat()) {
if (chat->inviteLink() == from) {
chat->setInviteLink(to);
}
chat->setInviteLink(link);
} else if (const auto channel = peer->asChannel()) {
if (channel->inviteLink() == from) {
channel->setInviteLink(to);
}
channel->setInviteLink(link);
} else {
Unexpected("Peer in InviteLinks::editMainLink.");
}

View File

@ -18,6 +18,8 @@ struct InviteLink {
TimeId expireDate = 0;
int usageLimit = 0;
int usage = 0;
bool permanent = false;
bool expired = false;
bool revoked = false;
};
@ -35,14 +37,24 @@ public:
void create(
not_null<PeerData*> peer,
Fn<void(Link)> done = nullptr,
TimeId expireDate = 0,
int usageLimit = 0);
void edit(
not_null<PeerData*> peer,
const QString &link,
TimeId expireDate,
int usageLimit);
void revoke(not_null<PeerData*> peer, const QString &link);
int usageLimit,
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);
[[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(
not_null<PeerData*> peer,
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;
base::flat_map<not_null<PeerData*>, Links> _firstSlices;
base::flat_map<not_null<PeerData*>, mtpRequestId> _firstSliceRequests;
base::flat_map<not_null<PeerData*>, mtpRequestId> _createRequests;
base::flat_map<EditKey, mtpRequestId> _editRequests;
base::flat_map<
not_null<PeerData*>,
std::vector<Fn<void(Link)>>> _createCallbacks;
base::flat_map<EditKey, std::vector<Fn<void(Link)>>> _editCallbacks;
};

View File

@ -614,7 +614,9 @@ void GroupInfoBox::createGroup(
}
void GroupInfoBox::submit() {
if (_creationRequestId) return;
if (_creationRequestId || _creatingInviteLink) {
return;
}
auto title = TextUtilities::PrepareForSending(_title->getLastText());
auto description = _description
@ -653,6 +655,8 @@ void GroupInfoBox::submit() {
}
void GroupInfoBox::createChannel(const QString &title, const QString &description) {
Expects(!_creationRequestId);
const auto flags = (_type == Type::Megagroup)
? MTPchannels_CreateChannel::Flag::f_megagroup
: MTPchannels_CreateChannel::Flag::f_broadcast;
@ -692,29 +696,9 @@ void GroupInfoBox::createChannel(const QString &title, const QString &descriptio
channel,
std::move(image));
}
using Flag = MTPmessages_ExportChatInvite::Flag;
_createdChannel = channel;
_creationRequestId = _api.request(MTPmessages_ExportChatInvite(
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();
checkInviteLink();
};
if (!success) {
LOG(("API Error: channel not found in updates (GroupInfoBox::creationDone)"));
@ -733,6 +717,32 @@ void GroupInfoBox::createChannel(const QString &title, const QString &descriptio
}).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() {
updateMaxHeight();
update();
@ -834,7 +844,7 @@ void SetupChannelBox::prepare() {
_channel->session().changes().peerUpdates(
_channel,
Data::PeerUpdate::Flag::InviteLink
Data::PeerUpdate::Flag::InviteLinks
) | rpl::start_with_next([=] {
rtlupdate(_invitationLink);
}, lifetime());

View File

@ -121,6 +121,8 @@ private:
void createGroup(not_null<PeerListBox*> selectUsersBox, const QString &title, const std::vector<not_null<PeerData*>> &users);
void submitName();
void submit();
void checkInviteLink();
void channelReady();
void descriptionResized();
void updateMaxHeight();
@ -138,6 +140,7 @@ private:
// group / channel creation
mtpRequestId _creationRequestId = 0;
bool _creatingInviteLink = false;
ChannelData *_createdChannel = nullptr;
};

View File

@ -394,7 +394,7 @@ void MaxInviteBox::prepare() {
_channel->session().changes().peerUpdates(
_channel,
Data::PeerUpdate::Flag::InviteLink
Data::PeerUpdate::Flag::InviteLinks
) | rpl::start_with_next([=] {
rtlupdate(_invitationLink);
}, lifetime());

View File

@ -331,7 +331,6 @@ private:
void showEditLinkedChatBox();
void fillPrivacyTypeButton();
void fillLinkedChatButton();
void fillInviteLinkButton();
void fillSignaturesButton();
void fillHistoryVisibilityButton();
void fillManageSection();
@ -640,6 +639,8 @@ void Controller::refreshHistoryVisibility(anim::type animated) {
void Controller::showEditPeerTypeBox(
std::optional<rpl::producer<QString>> error) {
Expects(_privacySavedValue.has_value());
const auto boxCallback = crl::guard(this, [=](
Privacy checked, QString publicLink) {
_privacyTypeUpdates.fire(std::move(checked));
@ -652,7 +653,7 @@ void Controller::showEditPeerTypeBox(
_peer,
_channelHasLocationOriginalValue,
boxCallback,
_privacySavedValue,
*_privacySavedValue,
_usernameSavedValue,
error),
Ui::LayerOption::KeepOther);
@ -798,22 +799,9 @@ void Controller::fillLinkedChatButton() {
_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() {
Expects(_controls.buttonsLayout != nullptr);
const auto channel = _peer->asChannel();
if (!channel) return;
@ -964,8 +952,6 @@ void Controller::fillManageSection() {
if (canEditUsername) {
fillPrivacyTypeButton();
} else if (canEditInviteLink) {
fillInviteLinkButton();
}
if (canViewOrEditLinkedChat) {
fillLinkedChatButton();
@ -1010,7 +996,7 @@ void Controller::fillManageSection() {
peer->session().api().inviteLinks().requestLinks(peer);
return peer->session().changes().peerUpdates(
peer,
Data::PeerUpdate::Flag::InviteLink
Data::PeerUpdate::Flag::InviteLinks
) | rpl::map([=] {
return peer->session().api().inviteLinks().links(
peer).count;
@ -1018,7 +1004,7 @@ void Controller::fillManageSection() {
}) | rpl::flatten_latest(
) | ToPositiveNumberString(),
[=] { ShowEditInviteLinks(_navigation, _peer); },
st::infoIconPermissions);
st::infoIconInviteLinks);
}
if (canViewAdmins) {
AddButtonWithCount(

View File

@ -58,7 +58,7 @@ public:
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
bool useLocationPhrases,
std::optional<Privacy> privacySavedValue,
Privacy privacySavedValue,
std::optional<QString> usernameSavedValue);
void createContent();
@ -66,17 +66,11 @@ public:
void setFocusUsername();
rpl::producer<QString> getTitle() {
return _isInviteLink
? tr::lng_profile_invite_link_section()
: _isGroup
return _isGroup
? tr::lng_manage_peer_group_type()
: tr::lng_manage_peer_channel_type();
}
bool isInviteLink() {
return _isInviteLink;
}
bool isAllowSave() {
return _isAllowSave;
}
@ -98,17 +92,14 @@ private:
base::unique_qptr<Ui::FlatLabel> usernameResult;
const style::FlatLabel *usernameResultStyle = nullptr;
Ui::SlideWrap<Ui::RpWidget> *createInviteLinkWrap = nullptr;
Ui::SlideWrap<Ui::RpWidget> *editInviteLinkWrap = nullptr;
Ui::SlideWrap<Ui::RpWidget> *inviteLinkWrap = nullptr;
Ui::FlatLabel *inviteLink = nullptr;
};
Controls _controls;
object_ptr<Ui::RpWidget> createPrivaciesEdit();
object_ptr<Ui::RpWidget> createUsernameEdit();
object_ptr<Ui::RpWidget> createInviteLinkCreate();
object_ptr<Ui::RpWidget> createInviteLinkEdit();
object_ptr<Ui::RpWidget> createInviteLinkBlock();
void observeInviteLink();
@ -124,8 +115,7 @@ private:
not_null<const style::FlatLabel*> st);
bool canEditInviteLink() const;
void refreshEditInviteLink();
void refreshCreateInviteLink();
void refreshInviteLinkBlock();
void createInviteLink();
void revokeInviteLink(const QString &link);
@ -143,12 +133,11 @@ private:
not_null<PeerData*> _peer;
MTP::Sender _api;
std::optional<Privacy> _privacySavedValue;
Privacy _privacySavedValue = Privacy();
std::optional<QString> _usernameSavedValue;
bool _useLocationPhrases = false;
bool _isGroup = false;
bool _isInviteLink = false;
bool _isAllowSave = false;
base::unique_qptr<Ui::VerticalLayout> _wrap;
@ -165,7 +154,7 @@ Controller::Controller(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
bool useLocationPhrases,
std::optional<Privacy> privacySavedValue,
Privacy privacySavedValue,
std::optional<QString> usernameSavedValue)
: _peer(peer)
, _api(&_peer->session().mtp())
@ -173,8 +162,6 @@ Controller::Controller(
, _usernameSavedValue(usernameSavedValue)
, _useLocationPhrases(useLocationPhrases)
, _isGroup(_peer->isChat() || _peer->isMegagroup())
, _isInviteLink(!_privacySavedValue.has_value()
&& !_usernameSavedValue.has_value())
, _isAllowSave(!_usernameSavedValue.value_or(QString()).isEmpty())
, _wrap(container)
, _checkUsernameTimer([=] { checkUsernameAvailability(); }) {
@ -184,18 +171,11 @@ Controller::Controller(
void Controller::createContent() {
_controls = Controls();
if (_isInviteLink) {
_wrap->add(createInviteLinkCreate());
_wrap->add(createInviteLinkEdit());
return;
}
fillPrivaciesButtons(_wrap, _privacySavedValue);
// Skip.
_wrap->add(object_ptr<Ui::BoxContentDivider>(_wrap));
//
_wrap->add(createInviteLinkCreate());
_wrap->add(createInviteLinkEdit());
_wrap->add(createInviteLinkBlock());
_wrap->add(createUsernameEdit());
if (_controls.privacy->value() == Privacy::NoUsername) {
@ -378,16 +358,14 @@ void Controller::privacyChanged(Privacy value) {
// Otherwise box will change own Y position.
if (value == Privacy::HasUsername) {
refreshCreateInviteLink();
refreshEditInviteLink();
refreshInviteLinkBlock();
toggleEditUsername();
_controls.usernameResult = nullptr;
checkUsernameAvailability();
} else {
toggleEditUsername();
refreshCreateInviteLink();
refreshEditInviteLink();
refreshInviteLinkBlock();
}
};
if (value == Privacy::HasUsername) {
@ -562,29 +540,26 @@ void Controller::revokeInviteLink(const QString &link) {
bool Controller::canEditInviteLink() const {
if (const auto channel = _peer->asChannel()) {
return channel->amCreator()
|| (channel->adminRights() & ChatAdminRight::f_invite_users);
return channel->canHaveInviteLink();
} else if (const auto chat = _peer->asChat()) {
return chat->amCreator()
|| (chat->adminRights() & ChatAdminRight::f_invite_users);
return chat->canHaveInviteLink();
}
return false;
}
void Controller::observeInviteLink() {
if (!_controls.editInviteLinkWrap) {
if (!_controls.inviteLinkWrap) {
return;
}
_peer->session().changes().peerFlagsValue(
_peer,
Data::PeerUpdate::Flag::InviteLink
Data::PeerUpdate::Flag::InviteLinks
) | rpl::start_with_next([=] {
refreshCreateInviteLink();
refreshEditInviteLink();
}, _controls.editInviteLinkWrap->lifetime());
refreshInviteLinkBlock();
}, _controls.inviteLinkWrap->lifetime());
}
object_ptr<Ui::RpWidget> Controller::createInviteLinkEdit() {
object_ptr<Ui::RpWidget> Controller::createInviteLinkBlock() {
Expects(_wrap != nullptr);
if (!canEditInviteLink()) {
@ -595,18 +570,16 @@ object_ptr<Ui::RpWidget> Controller::createInviteLinkEdit() {
_wrap,
object_ptr<Ui::VerticalLayout>(_wrap),
st::editPeerInvitesMargins);
_controls.editInviteLinkWrap = result.data();
_controls.inviteLinkWrap = result.data();
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::editPeerInviteLinkBoxBottomSkip));
}
container->add(object_ptr<Ui::FlatLabel>(
container,
tr::lng_profile_invite_link_section(),
st::editPeerSectionLabel));
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::editPeerInviteLinkBoxBottomSkip));
_controls.inviteLink = container->add(object_ptr<Ui::FlatLabel>(
container,
@ -634,7 +607,7 @@ object_ptr<Ui::RpWidget> Controller::createInviteLinkEdit() {
return result;
}
void Controller::refreshEditInviteLink() {
void Controller::refreshInviteLinkBlock() {
const auto link = inviteLinkText();
auto text = TextWithEntities();
if (!link.isEmpty()) {
@ -652,74 +625,26 @@ void Controller::refreshEditInviteLink() {
_controls.inviteLink->setMarkedText(text);
// 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(),
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() {
return !_controls.privacy
|| (_controls.privacy->value() == Privacy::NoUsername)
|| _isInviteLink;
|| (_controls.privacy->value() == Privacy::NoUsername);
}
} // namespace
EditPeerTypeBox::EditPeerTypeBox(QWidget*, not_null<PeerData*> peer)
: EditPeerTypeBox(nullptr, peer, false, {}, {}, {}, {}) {
}
EditPeerTypeBox::EditPeerTypeBox(
QWidget*,
not_null<PeerData*> peer,
bool useLocationPhrases,
std::optional<FnMut<void(Privacy, QString)>> savedCallback,
std::optional<Privacy> privacySaved,
Privacy privacySaved,
std::optional<QString> usernameSaved,
std::optional<rpl::producer<QString>> usernameError)
: _peer(peer)
@ -760,7 +685,7 @@ void EditPeerTypeBox::prepare() {
setTitle(controller->getTitle());
if (!controller->isInviteLink() && _savedCallback.has_value()) {
if (_savedCallback.has_value()) {
addButton(tr::lng_settings_save(), [=] {
const auto v = controller->getPrivacy();
if (!controller->isAllowSave() && (v == Privacy::HasUsername)) {
@ -772,13 +697,11 @@ void EditPeerTypeBox::prepare() {
local(v,
(v == Privacy::HasUsername)
? controller->getUsernameInput()
: QString()); // We dont need username with private type.
: QString()); // We don't need username with private type.
closeBox();
});
}
addButton(
controller->isInviteLink() ? tr::lng_close() : tr::lng_cancel(),
[=] { closeBox(); });
addButton(tr::lng_cancel(), [=] { closeBox(); });
setDimensionsToContent(st::boxWideWidth, content);
}

View File

@ -32,15 +32,12 @@ enum class UsernameState {
class EditPeerTypeBox : public Ui::BoxContent {
public:
// Edit just the invite link.
EditPeerTypeBox(QWidget*, not_null<PeerData*> peer);
EditPeerTypeBox(
QWidget*,
not_null<PeerData*> peer,
bool useLocationPhrases,
std::optional<FnMut<void(Privacy, QString)>> savedCallback,
std::optional<Privacy> privacySaved,
Privacy privacySaved,
std::optional<QString> usernameSaved,
std::optional<rpl::producer<QString>> usernameError = {});
@ -53,7 +50,7 @@ private:
bool _useLocationPhrases = false;
std::optional<FnMut<void(Privacy, QString)>> _savedCallback;
std::optional<Privacy> _privacySavedValue;
Privacy _privacySavedValue = Privacy();
std::optional<QString> _usernameSavedValue;
std::optional<rpl::producer<QString>> _usernameError;

View File

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_group_call.h"
#include "data/data_changes.h"
#include "core/application.h"
#include "boxes/single_choice_box.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 "main/main_session.h"
#include "apiwrap.h"
#include "api/api_invite_links.h"
#include "styles/style_layers.h"
#include "styles/style_calls.h"
#include "styles/style_settings.h"
@ -441,23 +443,9 @@ void GroupCallSettingsBox(
)->addClickHandler([=] {
if (!copyLink() && !state->generatingLink) {
state->generatingLink = true;
peer->session().api().request(MTPmessages_ExportChatInvite(
MTP_flags(0),
peer->input,
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();
peer->session().api().inviteLinks().create(
peer,
crl::guard(layout, [=](auto&&) { copyLink(); }));
}
});
}

View File

@ -74,7 +74,7 @@ struct PeerUpdate {
IsBot = (1 << 19),
// For chats and channels
InviteLink = (1 << 20),
InviteLinks = (1 << 20),
Members = (1 << 21),
Admins = (1 << 22),
BannedUsers = (1 << 23),

View File

@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "main/main_session.h"
#include "api/api_chat_invite.h"
#include "api/api_invite_links.h"
#include "apiwrap.h"
namespace {
@ -98,10 +99,7 @@ void ChannelData::setAccessHash(uint64 accessHash) {
}
void ChannelData::setInviteLink(const QString &newInviteLink) {
if (newInviteLink != _inviteLink) {
_inviteLink = newInviteLink;
session().changes().peerUpdated(this, UpdateFlag::InviteLink);
}
_inviteLink = newInviteLink;
}
bool ChannelData::canHaveInviteLink() const {
@ -778,11 +776,11 @@ void ApplyChannelUpdate(
next->v - channel->slowmodeSeconds());
}
if (const auto invite = update.vexported_invite()) {
invite->match([&](const MTPDchatInviteExported &data) {
channel->setInviteLink(qs(data.vlink()));
});
channel->session().api().inviteLinks().setPermanent(
channel,
*invite);
} else {
channel->setInviteLink(QString());
channel->session().api().inviteLinks().clearPermanent(channel);
}
if (const auto location = update.vlocation()) {
channel->setLocation(*location);

View File

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "api/api_invite_links.h"
namespace {
@ -127,10 +128,7 @@ void ChatData::invalidateParticipants() {
}
void ChatData::setInviteLink(const QString &newInviteLink) {
if (newInviteLink != _inviteLink) {
_inviteLink = newInviteLink;
session().changes().peerUpdated(this, UpdateFlag::InviteLink);
}
_inviteLink = newInviteLink;
}
bool ChatData::canHaveInviteLink() const {
@ -389,11 +387,9 @@ void ApplyChatUpdate(not_null<ChatData*> chat, const MTPDchatFull &update) {
chat->setUserpicPhoto(MTP_photoEmpty(MTP_long(0)));
}
if (const auto invite = update.vexported_invite()) {
invite->match([&](const MTPDchatInviteExported &data) {
chat->setInviteLink(qs(data.vlink()));
});
chat->session().api().inviteLinks().setPermanent(chat, *invite);
} else {
chat->setInviteLink(QString());
chat->session().api().inviteLinks().clearPermanent(chat);
}
if (const auto pinned = update.vpinned_msg_id()) {
SetTopPinnedMessageId(chat, pinned->v);

View File

@ -348,7 +348,7 @@ infoProfileSeparatorPadding: margins(
infoIconFg: menuIconFg;
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 }};
infoIconActions: icon {{ "info_actions", infoIconFg }};
//infoIconFeed: icon {{ "info_feed", infoIconFg }};
@ -360,10 +360,11 @@ infoIconMediaLink: icon {{ "info_media_link", infoIconFg }};
infoIconMediaGroup: icon {{ "info_common_groups", infoIconFg }};
infoIconMediaVoice: icon {{ "info_media_voice", infoIconFg }};
infoIconMediaRound: icon {{ "info_media_round", infoIconFg }};
infoIconRecentActions: icon {{ "info_recent_actions", infoIconFg, point(-2px, 0px) }};
infoIconAdministrators: icon {{ "info_administrators", infoIconFg, point(-2px, -1px) }};
infoIconRecentActions: icon {{ "info/edit/group_manage_actions", infoIconFg, point(-2px, -1px) }};
infoIconAdministrators: icon {{ "info/edit/group_manage_admins", infoIconFg, point(-3px, 0px) }};
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);
infoNotificationsIconPosition: point(20px, 5px);
infoSharedMediaIconPosition: point(20px, 24px);