diff --git a/Telegram/Resources/icons/info/edit/group_manage_actions.png b/Telegram/Resources/icons/info/edit/group_manage_actions.png new file mode 100644 index 000000000..3c1d15b2d Binary files /dev/null and b/Telegram/Resources/icons/info/edit/group_manage_actions.png differ diff --git a/Telegram/Resources/icons/info/edit/group_manage_actions@2x.png b/Telegram/Resources/icons/info/edit/group_manage_actions@2x.png new file mode 100644 index 000000000..b04ad81f4 Binary files /dev/null and b/Telegram/Resources/icons/info/edit/group_manage_actions@2x.png differ diff --git a/Telegram/Resources/icons/info/edit/group_manage_actions@3x.png b/Telegram/Resources/icons/info/edit/group_manage_actions@3x.png new file mode 100644 index 000000000..004040f69 Binary files /dev/null and b/Telegram/Resources/icons/info/edit/group_manage_actions@3x.png differ diff --git a/Telegram/Resources/icons/info/edit/group_manage_admins.png b/Telegram/Resources/icons/info/edit/group_manage_admins.png new file mode 100644 index 000000000..79c0cb7ca Binary files /dev/null and b/Telegram/Resources/icons/info/edit/group_manage_admins.png differ diff --git a/Telegram/Resources/icons/info/edit/group_manage_admins@2x.png b/Telegram/Resources/icons/info/edit/group_manage_admins@2x.png new file mode 100644 index 000000000..bfd3265c7 Binary files /dev/null and b/Telegram/Resources/icons/info/edit/group_manage_admins@2x.png differ diff --git a/Telegram/Resources/icons/info/edit/group_manage_admins@3x.png b/Telegram/Resources/icons/info/edit/group_manage_admins@3x.png new file mode 100644 index 000000000..c57d003cb Binary files /dev/null and b/Telegram/Resources/icons/info/edit/group_manage_admins@3x.png differ diff --git a/Telegram/Resources/icons/info/edit/group_manage_links.png b/Telegram/Resources/icons/info/edit/group_manage_links.png new file mode 100644 index 000000000..f865287de Binary files /dev/null and b/Telegram/Resources/icons/info/edit/group_manage_links.png differ diff --git a/Telegram/Resources/icons/info/edit/group_manage_links@2x.png b/Telegram/Resources/icons/info/edit/group_manage_links@2x.png new file mode 100644 index 000000000..671b6419b Binary files /dev/null and b/Telegram/Resources/icons/info/edit/group_manage_links@2x.png differ diff --git a/Telegram/Resources/icons/info/edit/group_manage_links@3x.png b/Telegram/Resources/icons/info/edit/group_manage_links@3x.png new file mode 100644 index 000000000..07258c5df Binary files /dev/null and b/Telegram/Resources/icons/info/edit/group_manage_links@3x.png differ diff --git a/Telegram/Resources/icons/info/edit/group_manage_members.png b/Telegram/Resources/icons/info/edit/group_manage_members.png new file mode 100644 index 000000000..8cf516c31 Binary files /dev/null and b/Telegram/Resources/icons/info/edit/group_manage_members.png differ diff --git a/Telegram/Resources/icons/info/edit/group_manage_members@2x.png b/Telegram/Resources/icons/info/edit/group_manage_members@2x.png new file mode 100644 index 000000000..f9c210aff Binary files /dev/null and b/Telegram/Resources/icons/info/edit/group_manage_members@2x.png differ diff --git a/Telegram/Resources/icons/info/edit/group_manage_members@3x.png b/Telegram/Resources/icons/info/edit/group_manage_members@3x.png new file mode 100644 index 000000000..220e567f3 Binary files /dev/null and b/Telegram/Resources/icons/info/edit/group_manage_members@3x.png differ diff --git a/Telegram/Resources/icons/info/edit/group_manage_permissions.png b/Telegram/Resources/icons/info/edit/group_manage_permissions.png new file mode 100644 index 000000000..1fcce1ee1 Binary files /dev/null and b/Telegram/Resources/icons/info/edit/group_manage_permissions.png differ diff --git a/Telegram/Resources/icons/info/edit/group_manage_permissions@2x.png b/Telegram/Resources/icons/info/edit/group_manage_permissions@2x.png new file mode 100644 index 000000000..594f89414 Binary files /dev/null and b/Telegram/Resources/icons/info/edit/group_manage_permissions@2x.png differ diff --git a/Telegram/Resources/icons/info/edit/group_manage_permissions@3x.png b/Telegram/Resources/icons/info/edit/group_manage_permissions@3x.png new file mode 100644 index 000000000..ae6732b32 Binary files /dev/null and b/Telegram/Resources/icons/info/edit/group_manage_permissions@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0afb64248..f78482907 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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"; diff --git a/Telegram/SourceFiles/api/api_invite_links.cpp b/Telegram/SourceFiles/api/api_invite_links.cpp index b8ece44a1..8e05c1b59 100644 --- a/Telegram/SourceFiles/api/api_invite_links.cpp +++ b/Telegram/SourceFiles/api/api_invite_links.cpp @@ -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 api) : _api(api) { @@ -28,93 +45,167 @@ InviteLinks::InviteLinks(not_null api) : _api(api) { void InviteLinks::create( not_null peer, + Fn done, TimeId expireDate, int usageLimit) { - if (_createRequests.contains(peer)) { + performCreate(peer, std::move(done), false, expireDate, usageLimit); +} + +void InviteLinks::performCreate( + not_null peer, + Fn 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 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 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 peer, const QString &link, TimeId expireDate, + int usageLimit, + Fn done) { + performEdit(peer, link, std::move(done), false, expireDate, usageLimit); +} + +void InviteLinks::performEdit( + not_null peer, + const QString &link, + Fn 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 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 peer, + const QString &link, + Fn done) { + performEdit(peer, link, std::move(done), true); } void InviteLinks::requestLinks(not_null peer) { @@ -129,16 +220,80 @@ void InviteLinks::requestLinks(not_null 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 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 peer) { + if (const auto permanent = lookupPermanent(peer)) { + permanent->revoked = true; + editPermanentLink(peer, QString()); + notify(peer); + } +} + +void InviteLinks::notify(not_null peer) { + peer->session().changes().peerUpdated( + peer, + Data::PeerUpdate::Flag::InviteLinks); +} + auto InviteLinks::links(not_null 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 peer) const -> Links { auto InviteLinks::parseSlice( not_null 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 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 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 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."); } diff --git a/Telegram/SourceFiles/api/api_invite_links.h b/Telegram/SourceFiles/api/api_invite_links.h index 7de6341be..f2534f3d3 100644 --- a/Telegram/SourceFiles/api/api_invite_links.h +++ b/Telegram/SourceFiles/api/api_invite_links.h @@ -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 peer, + Fn done = nullptr, TimeId expireDate = 0, int usageLimit = 0); void edit( not_null peer, const QString &link, TimeId expireDate, - int usageLimit); - void revoke(not_null peer, const QString &link); + int usageLimit, + Fn done = nullptr); + void revoke( + not_null peer, + const QString &link, + Fn done = nullptr); + + void setPermanent( + not_null peer, + const MTPExportedChatInvite &invite); + void clearPermanent(not_null peer); void requestLinks(not_null peer); [[nodiscard]] Links links(not_null peer) const; @@ -64,21 +76,47 @@ private: } }; - void editPermanentLink( - not_null peer, - const QString &from, - const QString &to); [[nodiscard]] Links parseSlice( not_null peer, const MTPmessages_ExportedChatInvites &slice) const; + [[nodiscard]] Link parse( + not_null peer, + const MTPExportedChatInvite &invite) const; + [[nodiscard]] Link *lookupPermanent(not_null peer); + [[nodiscard]] Link *lookupPermanent(Links &links); + [[nodiscard]] const Link *lookupPermanent(const Links &links) const; + Link prepend( + not_null peer, + const MTPExportedChatInvite &invite); + void notify(not_null peer); + + void editPermanentLink( + not_null peer, + const QString &link); + + void performEdit( + not_null peer, + const QString &link, + Fn done, + bool revoke, + TimeId expireDate = 0, + int usageLimit = 0); + void performCreate( + not_null peer, + Fn done, + bool revokeLegacyPermanent, + TimeId expireDate = 0, + int usageLimit = 0); const not_null _api; base::flat_map, Links> _firstSlices; base::flat_map, mtpRequestId> _firstSliceRequests; - base::flat_map, mtpRequestId> _createRequests; - base::flat_map _editRequests; + base::flat_map< + not_null, + std::vector>> _createCallbacks; + base::flat_map>> _editCallbacks; }; diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index 54e633fdc..ee74e1c52 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -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( - _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( + _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()); diff --git a/Telegram/SourceFiles/boxes/add_contact_box.h b/Telegram/SourceFiles/boxes/add_contact_box.h index d28e57450..39bb98717 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.h +++ b/Telegram/SourceFiles/boxes/add_contact_box.h @@ -121,6 +121,8 @@ private: void createGroup(not_null selectUsersBox, const QString &title, const std::vector> &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; }; diff --git a/Telegram/SourceFiles/boxes/confirm_box.cpp b/Telegram/SourceFiles/boxes/confirm_box.cpp index a9485ea86..e5b4f766d 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.cpp +++ b/Telegram/SourceFiles/boxes/confirm_box.cpp @@ -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()); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index edf4465bb..a3ae08f6e 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -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> 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(_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( diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp index 5b20229e2..170bb5dc9 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp @@ -58,7 +58,7 @@ public: not_null container, not_null peer, bool useLocationPhrases, - std::optional privacySavedValue, + Privacy privacySavedValue, std::optional usernameSavedValue); void createContent(); @@ -66,17 +66,11 @@ public: void setFocusUsername(); rpl::producer 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 usernameResult; const style::FlatLabel *usernameResultStyle = nullptr; - Ui::SlideWrap *createInviteLinkWrap = nullptr; - Ui::SlideWrap *editInviteLinkWrap = nullptr; + Ui::SlideWrap *inviteLinkWrap = nullptr; Ui::FlatLabel *inviteLink = nullptr; }; Controls _controls; - object_ptr createPrivaciesEdit(); object_ptr createUsernameEdit(); - object_ptr createInviteLinkCreate(); - object_ptr createInviteLinkEdit(); + object_ptr createInviteLinkBlock(); void observeInviteLink(); @@ -124,8 +115,7 @@ private: not_null st); bool canEditInviteLink() const; - void refreshEditInviteLink(); - void refreshCreateInviteLink(); + void refreshInviteLinkBlock(); void createInviteLink(); void revokeInviteLink(const QString &link); @@ -143,12 +133,11 @@ private: not_null _peer; MTP::Sender _api; - std::optional _privacySavedValue; + Privacy _privacySavedValue = Privacy(); std::optional _usernameSavedValue; bool _useLocationPhrases = false; bool _isGroup = false; - bool _isInviteLink = false; bool _isAllowSave = false; base::unique_qptr _wrap; @@ -165,7 +154,7 @@ Controller::Controller( not_null container, not_null peer, bool useLocationPhrases, - std::optional privacySavedValue, + Privacy privacySavedValue, std::optional 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(_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 Controller::createInviteLinkEdit() { +object_ptr Controller::createInviteLinkBlock() { Expects(_wrap != nullptr); if (!canEditInviteLink()) { @@ -595,18 +570,16 @@ object_ptr Controller::createInviteLinkEdit() { _wrap, object_ptr(_wrap), st::editPeerInvitesMargins); - _controls.editInviteLinkWrap = result.data(); + _controls.inviteLinkWrap = result.data(); const auto container = result->entity(); - if (!_isInviteLink) { - container->add(object_ptr( - container, - tr::lng_profile_invite_link_section(), - st::editPeerSectionLabel)); - container->add(object_ptr( - container, - st::editPeerInviteLinkBoxBottomSkip)); - } + container->add(object_ptr( + container, + tr::lng_profile_invite_link_section(), + st::editPeerSectionLabel)); + container->add(object_ptr( + container, + st::editPeerInviteLinkBoxBottomSkip)); _controls.inviteLink = container->add(object_ptr( container, @@ -634,7 +607,7 @@ object_ptr 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 Controller::createInviteLinkCreate() { - Expects(_wrap != nullptr); - - if (!canEditInviteLink()) { - return nullptr; - } - - auto result = object_ptr>( - _wrap, - object_ptr(_wrap), - st::editPeerInvitesMargins); - const auto container = result->entity(); - - if (!_isInviteLink) { - container->add(object_ptr( - container, - tr::lng_profile_invite_link_section(), - st::editPeerSectionLabel)); - container->add(object_ptr( - container, - st::editPeerInviteLinkSkip)); - } - - container->add(object_ptr( - _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 peer) -: EditPeerTypeBox(nullptr, peer, false, {}, {}, {}, {}) { -} - EditPeerTypeBox::EditPeerTypeBox( QWidget*, not_null peer, bool useLocationPhrases, std::optional> savedCallback, - std::optional privacySaved, + Privacy privacySaved, std::optional usernameSaved, std::optional> 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); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h index 7e0e6c620..8aae656e2 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h @@ -32,15 +32,12 @@ enum class UsernameState { class EditPeerTypeBox : public Ui::BoxContent { public: - // Edit just the invite link. - EditPeerTypeBox(QWidget*, not_null peer); - EditPeerTypeBox( QWidget*, not_null peer, bool useLocationPhrases, std::optional> savedCallback, - std::optional privacySaved, + Privacy privacySaved, std::optional usernameSaved, std::optional> usernameError = {}); @@ -53,7 +50,7 @@ private: bool _useLocationPhrases = false; std::optional> _savedCallback; - std::optional _privacySavedValue; + Privacy _privacySavedValue = Privacy(); std::optional _usernameSavedValue; std::optional> _usernameError; diff --git a/Telegram/SourceFiles/calls/calls_group_settings.cpp b/Telegram/SourceFiles/calls/calls_group_settings.cpp index 97d52424d..1ff199880 100644 --- a/Telegram/SourceFiles/calls/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/calls_group_settings.cpp @@ -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(); })); } }); } diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index 568840d82..cf00ebec3 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -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), diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index d21af2b41..6bc11d7d5 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -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); diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index c35953c72..8f4f29659 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -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 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); diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index bcf621494..72601f186 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -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);