diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 00814986b..8c4c9892c 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1216,6 +1216,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_invite_custom_limit" = "Enter custom limit"; "lng_group_invite_usage_any" = "No limit"; "lng_group_invite_usage_custom" = "Custom"; +"lng_group_invite_other_title" = "Invite links created by other admins"; +"lng_group_invite_other_count#one" = "{count} invite link"; +"lng_group_invite_other_count#other" = "{count} invite links"; +"lng_group_invite_permanent_other" = "Permanent link of this admin"; +"lng_group_invite_other_list" = "Invite links created by this admin"; "lng_channel_public_link_copied" = "Link copied to clipboard."; "lng_context_about_private_link" = "This link will only work for members of this chat."; diff --git a/Telegram/SourceFiles/api/api_invite_links.cpp b/Telegram/SourceFiles/api/api_invite_links.cpp index e43ad32a7..84719efb7 100644 --- a/Telegram/SourceFiles/api/api_invite_links.cpp +++ b/Telegram/SourceFiles/api/api_invite_links.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_invite_links.h" #include "data/data_peer.h" +#include "data/data_user.h" #include "data/data_chat.h" #include "data/data_channel.h" #include "data/data_session.h" @@ -103,7 +104,7 @@ void InviteLinks::performCreate( MTP_int(usageLimit) )).done([=](const MTPExportedChatInvite &result) { const auto callbacks = _createCallbacks.take(peer); - const auto link = prepend(peer, result); + const auto link = prepend(peer, peer->session().user(), result); if (callbacks) { for (const auto &callback : *callbacks) { callback(link); @@ -135,6 +136,7 @@ auto InviteLinks::lookupPermanent(const Links &links) const -> const Link* { auto InviteLinks::prepend( not_null peer, + not_null admin, const MTPExportedChatInvite &invite) -> Link { const auto link = parse(peer, invite); auto i = _firstSlices.find(peer); @@ -144,7 +146,10 @@ auto InviteLinks::prepend( auto &links = i->second; const auto permanent = lookupPermanent(links); const auto hadPermanent = (permanent != nullptr); - auto updateOldPermanent = Update{ .peer = peer }; + auto updateOldPermanent = Update{ + .peer = peer, + .admin = admin, + }; if (link.permanent && hadPermanent) { updateOldPermanent.was = permanent->link; updateOldPermanent.now = *permanent; @@ -171,21 +176,34 @@ auto InviteLinks::prepend( if (updateOldPermanent.now) { _updates.fire(std::move(updateOldPermanent)); } - _updates.fire(Update{ .peer = peer, .now = link }); + _updates.fire(Update{ + .peer = peer, + .admin = admin, + .now = link + }); return link; } void InviteLinks::edit( not_null peer, + not_null admin, const QString &link, TimeId expireDate, int usageLimit, Fn done) { - performEdit(peer, link, std::move(done), false, expireDate, usageLimit); + performEdit( + peer, + admin, + link, + std::move(done), + false, + expireDate, + usageLimit); } void InviteLinks::performEdit( not_null peer, + not_null admin, const QString &link, Fn done, bool revoke, @@ -202,15 +220,6 @@ void InviteLinks::performEdit( return; } - 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; - } - } - auto &callbacks = _editCallbacks[key]; if (done) { callbacks.push_back(std::move(done)); @@ -252,13 +261,14 @@ void InviteLinks::performEdit( } _updates.fire(Update{ .peer = peer, + .admin = admin, .was = key.link, .now = link, }); using Replaced = MTPDmessages_exportedChatInviteReplaced; if constexpr (Replaced::Is()) { - prepend(peer, data.vnew_invite()); + prepend(peer, admin, data.vnew_invite()); } }); }).fail([=](const RPCError &error) { @@ -268,17 +278,22 @@ void InviteLinks::performEdit( void InviteLinks::revoke( not_null peer, + not_null admin, const QString &link, Fn done) { - performEdit(peer, link, std::move(done), true); + performEdit(peer, admin, link, std::move(done), true); } void InviteLinks::revokePermanent( not_null peer, + not_null admin, + const QString &link, Fn done) { const auto callback = [=](auto&&) { done(); }; - if (const auto permanent = lookupPermanent(peer)) { - performEdit(peer, permanent->link, callback, true); + if (!link.isEmpty()) { + performEdit(peer, admin, link, callback, true); + } else if (!admin->isSelf()) { + crl::on_main(&peer->session(), done); } else { performCreate(peer, callback, true); } @@ -286,6 +301,7 @@ void InviteLinks::revokePermanent( void InviteLinks::destroy( not_null peer, + not_null admin, const QString &link, Fn done) { const auto key = LinkKey{ peer, link }; @@ -314,6 +330,7 @@ void InviteLinks::destroy( } _updates.fire(Update{ .peer = peer, + .admin = admin, .was = key.link, }); }).fail([=](const RPCError &error) { @@ -323,6 +340,7 @@ void InviteLinks::destroy( void InviteLinks::destroyAllRevoked( not_null peer, + not_null admin, Fn done) { if (const auto i = _deleteRevokedCallbacks.find(peer) ; i != end(_deleteRevokedCallbacks)) { @@ -337,7 +355,7 @@ void InviteLinks::destroyAllRevoked( } _api->request(MTPmessages_DeleteRevokedExportedChatInvites( peer->input, - MTP_inputUserSelf() + admin->inputUser )).done([=](const MTPBool &result) { if (const auto callbacks = _deleteRevokedCallbacks.take(peer)) { for (const auto &callback : *callbacks) { @@ -349,7 +367,7 @@ void InviteLinks::destroyAllRevoked( }).send(); } -void InviteLinks::requestLinks(not_null peer) { +void InviteLinks::requestMyLinks(not_null peer) { if (_firstSliceRequests.contains(peer)) { return; } @@ -469,7 +487,7 @@ void InviteLinks::requestJoinedFirstSlice(LinkKey key) { _firstJoinedRequests.emplace(key, requestId); } -void InviteLinks::setPermanent( +void InviteLinks::setMyPermanent( not_null peer, const MTPExportedChatInvite &invite) { auto link = parse(peer, invite); @@ -483,13 +501,17 @@ void InviteLinks::setPermanent( i = _firstSlices.emplace(peer).first; } auto &links = i->second; - auto updateOldPermanent = Update{ .peer = peer }; + auto updateOldPermanent = Update{ + .peer = peer, + .admin = peer->session().user(), + }; if (const auto permanent = lookupPermanent(links)) { if (permanent->link == link.link) { if (permanent->usage != link.usage) { permanent->usage = link.usage; _updates.fire(Update{ .peer = peer, + .admin = peer->session().user(), .was = link.link, .now = *permanent }); @@ -512,10 +534,14 @@ void InviteLinks::setPermanent( if (updateOldPermanent.now) { _updates.fire(std::move(updateOldPermanent)); } - _updates.fire(Update{ .peer = peer, .now = link }); + _updates.fire(Update{ + .peer = peer, + .admin = peer->session().user(), + .now = link + }); } -void InviteLinks::clearPermanent(not_null peer) { +void InviteLinks::clearMyPermanent(not_null peer) { auto i = _firstSlices.find(peer); if (i == end(_firstSlices)) { return; @@ -526,7 +552,10 @@ void InviteLinks::clearPermanent(not_null peer) { return; } - auto updateOldPermanent = Update{ .peer = peer }; + auto updateOldPermanent = Update{ + .peer = peer, + .admin = peer->session().user() + }; updateOldPermanent.was = permanent->link; updateOldPermanent.now = *permanent; updateOldPermanent.now->revoked = true; @@ -549,7 +578,7 @@ void InviteLinks::notify(not_null peer) { Data::PeerUpdate::Flag::InviteLinks); } -auto InviteLinks::links(not_null peer) const -> const Links & { +auto InviteLinks::myLinks(not_null peer) const -> const Links & { static const auto kEmpty = Links(); const auto i = _firstSlices.find(peer); return (i != end(_firstSlices)) ? i->second : kEmpty; @@ -596,6 +625,7 @@ auto InviteLinks::parse( void InviteLinks::requestMoreLinks( not_null peer, + not_null admin, TimeId lastDate, const QString &lastLink, bool revoked, @@ -605,14 +635,12 @@ void InviteLinks::requestMoreLinks( MTP_flags(Flag::f_offset_link | (revoked ? Flag::f_revoked : Flag(0))), peer->input, - MTP_inputUserSelf(), + admin->inputUser, MTP_int(lastDate), MTP_string(lastLink), MTP_int(kPerPage) )).done([=](const MTPmessages_ExportedChatInvites &result) { - auto slice = parseSlice(peer, result); - RemovePermanent(slice); - done(std::move(slice)); + done(parseSlice(peer, result)); }).fail([=](const RPCError &error) { done(Links()); }).send(); diff --git a/Telegram/SourceFiles/api/api_invite_links.h b/Telegram/SourceFiles/api/api_invite_links.h index e84447a42..024f7510c 100644 --- a/Telegram/SourceFiles/api/api_invite_links.h +++ b/Telegram/SourceFiles/api/api_invite_links.h @@ -40,6 +40,7 @@ struct JoinedByLinkSlice { struct InviteLinkUpdate { not_null peer; + not_null admin; QString was; std::optional now; }; @@ -63,32 +64,38 @@ public: int usageLimit = 0); void edit( not_null peer, + not_null admin, const QString &link, TimeId expireDate, int usageLimit, Fn done = nullptr); void revoke( not_null peer, + not_null admin, const QString &link, Fn done = nullptr); void revokePermanent( not_null peer, + not_null admin, + const QString &link, Fn done = nullptr); void destroy( not_null peer, + not_null admin, const QString &link, Fn done = nullptr); void destroyAllRevoked( not_null peer, + not_null admin, Fn done = nullptr); - void setPermanent( + void setMyPermanent( not_null peer, const MTPExportedChatInvite &invite); - void clearPermanent(not_null peer); + void clearMyPermanent(not_null peer); - void requestLinks(not_null peer); - [[nodiscard]] const Links &links(not_null peer) const; + void requestMyLinks(not_null peer); + [[nodiscard]] const Links &myLinks(not_null peer) const; [[nodiscard]] rpl::producer joinedFirstSliceValue( not_null peer, @@ -104,6 +111,7 @@ public: void requestMoreLinks( not_null peer, + not_null admin, TimeId lastDate, const QString &lastLink, bool revoked, @@ -135,6 +143,7 @@ private: [[nodiscard]] const Link *lookupPermanent(const Links &links) const; Link prepend( not_null peer, + not_null admin, const MTPExportedChatInvite &invite); void notify(not_null peer); @@ -144,6 +153,7 @@ private: void performEdit( not_null peer, + not_null admin, const QString &link, Fn done, bool revoke, diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index bf6f6596c..de3b9e09d 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -1002,18 +1002,18 @@ void Controller::fillManageSection() { Info::Profile::MigratedOrMeValue( _peer ) | rpl::map([=](not_null peer) { - peer->session().api().inviteLinks().requestLinks(peer); + peer->session().api().inviteLinks().requestMyLinks(peer); return peer->session().changes().peerUpdates( peer, Data::PeerUpdate::Flag::InviteLinks ) | rpl::map([=] { - return peer->session().api().inviteLinks().links( + return peer->session().api().inviteLinks().myLinks( peer).count; }); }) | rpl::flatten_latest( ) | ToPositiveNumberString(), [=] { Ui::show( - Box(ManageInviteLinksBox, _peer), + Box(ManageInviteLinksBox, _peer, _peer->session().user()), Ui::LayerOption::KeepOther); }, st::infoIconInviteLinks); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp index 12af5097a..fe6fc00af 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp @@ -102,7 +102,7 @@ void AddHeaderBlock( ShareInviteLinkBox(peer, link); }); const auto revokeLink = crl::guard(weak, [=] { - RevokeLink(peer, link); + RevokeLink(peer, data.admin, data.link); }); const auto createMenu = [=] { @@ -285,33 +285,44 @@ Main::Session &SingleRowController::session() const { void AddPermanentLinkBlock( not_null container, - not_null peer) { - const auto computePermanentLink = [=] { - const auto &links = peer->session().api().inviteLinks().links( - peer).links; - const auto link = links.empty() ? nullptr : &links.front(); - return (link && link->permanent && !link->revoked) ? link : nullptr; + not_null peer, + not_null admin, + rpl::producer fromList) { + struct LinkData { + QString link; + int usage = 0; }; - auto value = peer->session().changes().peerFlagsValue( - peer, - Data::PeerUpdate::Flag::InviteLinks - ) | rpl::map([=] { - const auto link = computePermanentLink(); - return link - ? std::make_tuple(link->link, link->usage) - : std::make_tuple(QString(), 0); - }) | rpl::distinct_until_changed( - ) | rpl::start_spawning(container->lifetime()); - + const auto value = container->lifetime().make_state< + rpl::variable + >(); + if (admin->isSelf()) { + *value = peer->session().changes().peerFlagsValue( + peer, + Data::PeerUpdate::Flag::InviteLinks + ) | rpl::map([=] { + const auto &links = peer->session().api().inviteLinks().myLinks( + peer).links; + const auto link = links.empty() ? nullptr : &links.front(); + return (link && link->permanent && !link->revoked) + ? LinkData{ link->link, link->usage } + : LinkData(); + }); + } else { + *value = std::move( + fromList + ) | rpl::map([](const Api::InviteLink &link) { + return LinkData{ link.link, link.usage }; + }); + } const auto weak = Ui::MakeWeak(container); const auto copyLink = crl::guard(weak, [=] { - if (const auto link = computePermanentLink()) { - CopyInviteLink(link->link); + if (const auto current = value->current(); !current.link.isEmpty()) { + CopyInviteLink(current.link); } }); const auto shareLink = crl::guard(weak, [=] { - if (const auto link = computePermanentLink()) { - ShareInviteLinkBox(peer, link->link); + if (const auto current = value->current(); !current.link.isEmpty()) { + ShareInviteLinkBox(peer, current.link); } }); const auto revokeLink = crl::guard(weak, [=] { @@ -322,18 +333,23 @@ void AddPermanentLinkBlock( (*box)->closeBox(); } }; - peer->session().api().inviteLinks().revokePermanent(peer, close); + peer->session().api().inviteLinks().revokePermanent( + peer, + admin, + value->current().link, + close); }); *box = Ui::show( Box(tr::lng_group_invite_about_new(tr::now), done), Ui::LayerOption::KeepOther); }); - auto link = rpl::duplicate( - value - ) | rpl::map([=](QString link, int usage) { + auto link = value->value( + ) | rpl::map([=](const LinkData &data) { const auto prefix = qstr("https://"); - return link.startsWith(prefix) ? link.mid(prefix.size()) : link; + return data.link.startsWith(prefix) + ? data.link.mid(prefix.size()) + : data.link; }); const auto createMenu = [=] { auto result = base::make_unique_q(container); @@ -389,13 +405,12 @@ void AddPermanentLinkBlock( .userpics = state->cachedUserpics }; }; - std::move( - value - ) | rpl::map([=](QString link, int usage) { + value->value( + ) | rpl::map([=](const LinkData &data) { return peer->session().api().inviteLinks().joinedFirstSliceValue( peer, - link, - usage); + data.link, + data.usage); }) | rpl::flatten_latest( ) | rpl::start_with_next([=](const Api::JoinedByLinkSlice &slice) { auto list = std::vector(); @@ -540,7 +555,10 @@ void ShareInviteLinkBox(not_null peer, const QString &link) { Ui::LayerOption::KeepOther); } -void RevokeLink(not_null peer, const QString &link) { +void RevokeLink( + not_null peer, + not_null admin, + const QString &link) { const auto box = std::make_shared>(); const auto revoke = [=] { const auto done = [=](const LinkData &data) { @@ -548,7 +566,7 @@ void RevokeLink(not_null peer, const QString &link) { (*box)->closeBox(); } }; - peer->session().api().inviteLinks().revoke(peer, link, done); + peer->session().api().inviteLinks().revoke(peer, admin, link, done); }; *box = Ui::show( Box( diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.h b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.h index abd353043..3cab90224 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.h @@ -21,11 +21,16 @@ class VerticalLayout; void AddPermanentLinkBlock( not_null container, - not_null peer); + not_null peer, + not_null admin, + rpl::producer fromList); void CopyInviteLink(const QString &link); void ShareInviteLinkBox(not_null peer, const QString &link); -void RevokeLink(not_null peer, const QString &link); +void RevokeLink( + not_null peer, + not_null admin, + const QString &link); void ShowInviteLinkBox( not_null peer, diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp index a28ea6fa1..a1c2ab3f2 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp @@ -8,6 +8,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_peer_invite_links.h" #include "data/data_peer.h" +#include "data/data_user.h" +#include "data/data_chat.h" +#include "data/data_channel.h" +#include "data/data_session.h" #include "main/main_session.h" #include "api/api_invite_links.h" #include "ui/boxes/edit_invite_link.h" @@ -167,7 +171,9 @@ private: return result; } -void EditLink(not_null peer, const InviteLinkData &data) { +void EditLink( + not_null peer, + const InviteLinkData &data) { const auto creating = data.link.isEmpty(); const auto box = std::make_shared>(); using Fields = Ui::InviteLinkFields; @@ -181,6 +187,7 @@ void EditLink(not_null peer, const InviteLinkData &data) { } }; if (creating) { + Assert(data.admin->isSelf()); peer->session().api().inviteLinks().create( peer, finish, @@ -189,6 +196,7 @@ void EditLink(not_null peer, const InviteLinkData &data) { } else { peer->session().api().inviteLinks().edit( peer, + data.admin, result.link, result.expireDate, result.usageLimit, @@ -209,7 +217,10 @@ void EditLink(not_null peer, const InviteLinkData &data) { Ui::LayerOption::KeepOther); } -void DeleteLink(not_null peer, const QString &link) { +void DeleteLink( + not_null peer, + not_null admin, + const QString &link) { const auto box = std::make_shared>(); const auto sure = [=] { const auto finish = [=] { @@ -217,14 +228,20 @@ void DeleteLink(not_null peer, const QString &link) { (*box)->closeBox(); } }; - peer->session().api().inviteLinks().destroy(peer, link, finish); + peer->session().api().inviteLinks().destroy( + peer, + admin, + link, + finish); }; *box = Ui::show( Box(tr::lng_group_invite_delete_sure(tr::now), sure), Ui::LayerOption::KeepOther); } -void DeleteAllRevoked(not_null peer) { +void DeleteAllRevoked( + not_null peer, + not_null admin) { const auto box = std::make_shared>(); const auto sure = [=] { const auto finish = [=] { @@ -232,7 +249,10 @@ void DeleteAllRevoked(not_null peer) { (*box)->closeBox(); } }; - peer->session().api().inviteLinks().destroyAllRevoked(peer, finish); + peer->session().api().inviteLinks().destroyAllRevoked( + peer, + admin, + finish); }; *box = Ui::show( Box(tr::lng_group_invite_delete_all_sure(tr::now), sure), @@ -365,12 +385,15 @@ void Row::paintAction( : st::inviteLinkThreeDotsIcon).paint(p, x, y, outerWidth); } -class Controller final +class LinksController final : public PeerListController , public RowDelegate , public base::has_weak_ptr { public: - Controller(not_null peer, bool revoked); + LinksController( + not_null peer, + not_null admin, + bool revoked); void prepare() override; void loadMoreRows() override; @@ -390,6 +413,10 @@ public: float64 progress, Color color) override; + [[nodiscard]] rpl::producer permanentFound() const { + return _permanentFound.events(); + } + private: void appendRow(const InviteLinkData &data, TimeId now); void prependRow(const InviteLinkData &data, TimeId now); @@ -405,6 +432,7 @@ private: not_null row); const not_null _peer; + const not_null _admin; const bool _revoked = false; base::unique_qptr _menu; @@ -413,6 +441,7 @@ private: bool _requesting = false; bool _allLoaded = false; + rpl::event_stream _permanentFound; base::flat_set> _expiringRows; base::Timer _updateExpiringTimer; @@ -421,8 +450,12 @@ private: }; -Controller::Controller(not_null peer, bool revoked) +LinksController::LinksController( + not_null peer, + not_null admin, + bool revoked) : _peer(peer) +, _admin(admin) , _revoked(revoked) , _updateExpiringTimer([=] { expiringProgressTimer(); }) { style::PaletteChanged( @@ -464,16 +497,16 @@ Controller::Controller(not_null peer, bool revoked) } } -void Controller::prepare() { - if (!_revoked) { - appendSlice(_peer->session().api().inviteLinks().links(_peer)); +void LinksController::prepare() { + if (!_revoked && _admin->isSelf()) { + appendSlice(_peer->session().api().inviteLinks().myLinks(_peer)); } if (!delegate()->peerListFullRowsCount()) { loadMoreRows(); } } -void Controller::loadMoreRows() { +void LinksController::loadMoreRows() { if (_requesting || _allLoaded) { return; } @@ -491,16 +524,19 @@ void Controller::loadMoreRows() { }; _peer->session().api().inviteLinks().requestMoreLinks( _peer, + _admin, _offsetDate, _offsetLink, _revoked, crl::guard(this, done)); } -void Controller::appendSlice(const InviteLinksSlice &slice) { +void LinksController::appendSlice(const InviteLinksSlice &slice) { const auto now = base::unixtime::now(); for (const auto &link : slice.links) { - if (!link.permanent || link.revoked) { + if (link.permanent && !link.revoked) { + _permanentFound.fire_copy(link); + } else { appendRow(link, now); } _offsetLink = link.link; @@ -512,15 +548,15 @@ void Controller::appendSlice(const InviteLinksSlice &slice) { delegate()->peerListRefreshRows(); } -void Controller::rowClicked(not_null row) { +void LinksController::rowClicked(not_null row) { ShowInviteLinkBox(_peer, static_cast(row.get())->data()); } -void Controller::rowActionClicked(not_null row) { +void LinksController::rowActionClicked(not_null row) { delegate()->peerListShowRowMenu(row, nullptr); } -base::unique_qptr Controller::rowContextMenu( +base::unique_qptr LinksController::rowContextMenu( QWidget *parent, not_null row) { auto result = createRowContextMenu(parent, row); @@ -537,7 +573,7 @@ base::unique_qptr Controller::rowContextMenu( return result; } -base::unique_qptr Controller::createRowContextMenu( +base::unique_qptr LinksController::createRowContextMenu( QWidget *parent, not_null row) { const auto real = static_cast(row.get()); @@ -546,7 +582,7 @@ base::unique_qptr Controller::createRowContextMenu( auto result = base::make_unique_q(parent); if (data.revoked) { result->addAction(tr::lng_group_invite_context_delete(tr::now), [=] { - DeleteLink(_peer, link); + DeleteLink(_peer, _admin, link); }); } else { result->addAction(tr::lng_group_invite_context_copy(tr::now), [=] { @@ -559,25 +595,25 @@ base::unique_qptr Controller::createRowContextMenu( EditLink(_peer, data); }); result->addAction(tr::lng_group_invite_context_revoke(tr::now), [=] { - RevokeLink(_peer, link); + RevokeLink(_peer, _admin, link); }); } return result; } -Main::Session &Controller::session() const { +Main::Session &LinksController::session() const { return _peer->session(); } -void Controller::appendRow(const InviteLinkData &data, TimeId now) { +void LinksController::appendRow(const InviteLinkData &data, TimeId now) { delegate()->peerListAppendRow(std::make_unique(this, data, now)); } -void Controller::prependRow(const InviteLinkData &data, TimeId now) { +void LinksController::prependRow(const InviteLinkData &data, TimeId now) { delegate()->peerListPrependRow(std::make_unique(this, data, now)); } -void Controller::updateRow(const InviteLinkData &data, TimeId now) { +void LinksController::updateRow(const InviteLinkData &data, TimeId now) { if (const auto row = delegate()->peerListFindRow(ComputeRowId(data))) { const auto real = static_cast(row); real->update(data, now); @@ -588,7 +624,7 @@ void Controller::updateRow(const InviteLinkData &data, TimeId now) { } } -bool Controller::removeRow(const QString &link) { +bool LinksController::removeRow(const QString &link) { if (const auto row = delegate()->peerListFindRow(ComputeRowId(link))) { delegate()->peerListRemoveRow(row); return true; @@ -596,7 +632,7 @@ bool Controller::removeRow(const QString &link) { return false; } -void Controller::checkExpiringTimer(not_null row) { +void LinksController::checkExpiringTimer(not_null row) { const auto updateIn = row->updateExpireIn(); if (updateIn > 0) { _expiringRows.emplace(row); @@ -609,7 +645,7 @@ void Controller::checkExpiringTimer(not_null row) { } } -void Controller::expiringProgressTimer() { +void LinksController::expiringProgressTimer() { const auto now = base::unixtime::now(); auto minimalIn = 0; for (auto i = begin(_expiringRows); i != end(_expiringRows);) { @@ -629,11 +665,11 @@ void Controller::expiringProgressTimer() { } } -void Controller::rowUpdateRow(not_null row) { +void LinksController::rowUpdateRow(not_null row) { delegate()->peerListUpdateRow(row); } -void Controller::rowPaintIcon( +void LinksController::rowPaintIcon( QPainter &p, int x, int y, @@ -650,7 +686,7 @@ void Controller::rowPaintIcon( case Color::Expired: return &st::msgFile3Bg; case Color::Revoked: return &st::windowSubTextFg; } - Unexpected("Color in Controller::rowPaintIcon."); + Unexpected("Color in LinksController::rowPaintIcon."); }(); auto &icon = _icons[int(color)]; if (icon.isNull()) { @@ -687,17 +723,111 @@ void Controller::rowPaintIcon( } } +class AdminsController final + : public PeerListController + , public base::has_weak_ptr { +public: + AdminsController(not_null peer, not_null admin); + ~AdminsController(); + + void prepare() override; + void loadMoreRows() override; + void rowClicked(not_null row) override; + Main::Session &session() const override; + +private: + void appendRow(not_null user, int count); + + const not_null _peer; + const not_null _admin; + mtpRequestId _requestId = 0; + +}; + +AdminsController::AdminsController( + not_null peer, + not_null admin) +: _peer(peer) +, _admin(admin) { +} + +AdminsController::~AdminsController() { + session().api().request(base::take(_requestId)).cancel(); +} + +void AdminsController::prepare() { + if (const auto chat = _peer->asChat()) { + if (!chat->amCreator()) { + return; + } + } else if (const auto channel = _peer->asChannel()) { + if (!channel->amCreator()) { + return; + } + } + if (!_admin->isSelf()) { + return; + } + _requestId = session().api().request(MTPmessages_GetAdminsWithInvites( + _peer->input + )).done([=](const MTPmessages_ChatAdminsWithInvites &result) { + result.match([&](const MTPDmessages_chatAdminsWithInvites &data) { + auto &owner = _peer->owner(); + owner.processUsers(data.vusers()); + for (const auto &admin : data.vadmins().v) { + admin.match([&](const MTPDchatAdminWithInvites &data) { + const auto adminId = data.vadmin_id().v; + if (const auto user = owner.userLoaded(adminId)) { + if (!user->isSelf()) { + appendRow(user, data.vinvites_count().v); + } + } + }); + } + delegate()->peerListRefreshRows(); + }); + }).send(); +} + +void AdminsController::loadMoreRows() { +} + +void AdminsController::rowClicked(not_null row) { + Ui::show( + Box(ManageInviteLinksBox, _peer, row->peer()->asUser()), + Ui::LayerOption::KeepOther); +} + +Main::Session &AdminsController::session() const { + return _peer->session(); +} + +void AdminsController::appendRow(not_null user, int count) { + auto row = std::make_unique(user); + row->setCustomStatus( + tr::lng_group_invite_other_count(tr::now, lt_count, count)); + delegate()->peerListAppendRow(std::move(row)); +} + } // namespace -not_null AddLinksList( +struct LinksList { + not_null widget; + rpl::producer permanentFound; +}; + +LinksList AddLinksList( not_null container, not_null peer, + not_null admin, bool revoked) { - const auto delegate = container->lifetime().make_state< + auto &lifetime = container->lifetime(); + const auto delegate = lifetime.make_state< PeerListContentDelegateSimple >(); - const auto controller = container->lifetime().make_state( + const auto controller = lifetime.make_state( peer, + admin, revoked); controller->setStyleOverrides(&st::inviteLinkList); const auto content = container->add(object_ptr( @@ -706,27 +836,67 @@ not_null AddLinksList( delegate->setContent(content); controller->setDelegate(delegate); + return { content, controller->permanentFound() }; +} + +not_null AddAdminsList( + not_null container, + not_null peer, + not_null admin) { + auto &lifetime = container->lifetime(); + const auto delegate = lifetime.make_state< + PeerListContentDelegateSimple + >(); + const auto controller = lifetime.make_state( + peer, + admin); + controller->setStyleOverrides(&st::inviteLinkList); + const auto content = container->add(object_ptr( + container, + controller)); + delegate->setContent(content); + controller->setDelegate(delegate); + return content; } void ManageInviteLinksBox( not_null box, - not_null peer) { + not_null peer, + not_null admin) { using namespace Settings; box->setTitle(tr::lng_group_invite_title()); const auto container = box->verticalLayout(); + const auto permanentFromList = box->lifetime().make_state< + rpl::event_stream + >(); AddSubsectionTitle(container, tr::lng_create_permanent_link_title()); - AddPermanentLinkBlock(container, peer); + AddPermanentLinkBlock( + container, + peer, + admin, + permanentFromList->events()); AddDivider(container); - const auto add = AddCreateLinkButton(container); - add->setClickedCallback([=] { - EditLink(peer, InviteLinkData{ .admin = peer->session().user() }); - }); + if (admin->isSelf()) { + const auto add = AddCreateLinkButton(container); + add->setClickedCallback([=] { + EditLink(peer, InviteLinkData{ .admin = admin }); + }); + } else { + AddSubsectionTitle(container, tr::lng_group_invite_other_list()); + } + + auto [list, newPermanent] = AddLinksList(container, peer, admin, false); + + std::move( + newPermanent + ) | rpl::start_with_next([=](InviteLinkData &&data) { + permanentFromList->fire(std::move(data)); + }, container->lifetime()); - const auto list = AddLinksList(container, peer, false); const auto dividerAbout = container->add(object_ptr>( container, object_ptr( @@ -737,24 +907,37 @@ void ManageInviteLinksBox( st::boxDividerLabel), st::settingsDividerLabelPadding)), style::margins(0, st::inviteLinkCreateSkip, 0, 0)); - const auto divider = container->add(object_ptr>( + + const auto adminsDivider = container->add(object_ptr>( container, object_ptr(container))); - const auto header = container->add(object_ptr>( + const auto adminsHeader = container->add(object_ptr>( + container, + object_ptr( + container, + tr::lng_group_invite_other_title(), + st::settingsSubsectionTitle), + st::inviteLinkRevokedTitlePadding)); + const auto admins = AddAdminsList(container, peer, admin); + + const auto revokedDivider = container->add(object_ptr>( + container, + object_ptr(container))); + const auto revokedHeader = container->add(object_ptr>( container, object_ptr( container, tr::lng_group_invite_revoked_title(), st::settingsSubsectionTitle), st::inviteLinkRevokedTitlePadding)); - const auto revoked = AddLinksList(container, peer, true); + const auto revoked = AddLinksList(container, peer, admin, true).widget; const auto deleteAll = Ui::CreateChild( container.get(), tr::lng_group_invite_context_delete_all(tr::now), st::defaultLinkButton); rpl::combine( - header->topValue(), + revokedHeader->topValue(), container->widthValue() ) | rpl::start_with_next([=](int top, int outerWidth) { deleteAll->moveToRight( @@ -763,18 +946,21 @@ void ManageInviteLinksBox( outerWidth); }, deleteAll->lifetime()); deleteAll->setClickedCallback([=] { - DeleteAllRevoked(peer); + DeleteAllRevoked(peer, admin); }); rpl::combine( list->heightValue(), + admins->heightValue(), revoked->heightValue() - ) | rpl::start_with_next([=](int list, int revoked) { + ) | rpl::start_with_next([=](int list, int admins, int revoked) { dividerAbout->toggle(!list, anim::type::instant); - divider->toggle(list > 0 && revoked > 0, anim::type::instant); - header->toggle(revoked > 0, anim::type::instant); + adminsDivider->toggle(admins > 0 && list > 0, anim::type::instant); + adminsHeader->toggle(admins > 0, anim::type::instant); + revokedDivider->toggle(revoked > 0 && (list > 0 || admins > 0), anim::type::instant); + revokedHeader->toggle(revoked > 0, anim::type::instant); deleteAll->setVisible(revoked > 0); - }, header->lifetime()); + }, revokedHeader->lifetime()); box->addButton(tr::lng_about_done(), [=] { box->closeBox(); }); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.h b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.h index 09b2880c3..2f1050317 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.h @@ -13,4 +13,5 @@ class PeerData; void ManageInviteLinksBox( not_null box, - not_null peer); + not_null peer, + not_null admin); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp index dcc40a5ee..52e11b80c 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp @@ -193,7 +193,7 @@ void Controller::createContent() { tr::lng_group_invite_manage(), rpl::single(QString()), [=] { Ui::show( - Box(ManageInviteLinksBox, _peer), + Box(ManageInviteLinksBox, _peer, _peer->session().user()), Ui::LayerOption::KeepOther); }, st::manageGroupButton, @@ -566,7 +566,11 @@ object_ptr Controller::createInviteLinkBlock() { AddSubsectionTitle(container, tr::lng_create_permanent_link_title()); } - AddPermanentLinkBlock(container, _peer); + AddPermanentLinkBlock( + container, + _peer, + _peer->session().user(), + nullptr); AddSkip(container); diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 6bc11d7d5..72a9e7788 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -776,11 +776,11 @@ void ApplyChannelUpdate( next->v - channel->slowmodeSeconds()); } if (const auto invite = update.vexported_invite()) { - channel->session().api().inviteLinks().setPermanent( + channel->session().api().inviteLinks().setMyPermanent( channel, *invite); } else { - channel->session().api().inviteLinks().clearPermanent(channel); + channel->session().api().inviteLinks().clearMyPermanent(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 b0ec13d23..d554877ae 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -390,9 +390,9 @@ void ApplyChatUpdate(not_null chat, const MTPDchatFull &update) { chat->setUserpicPhoto(MTP_photoEmpty(MTP_long(0))); } if (const auto invite = update.vexported_invite()) { - chat->session().api().inviteLinks().setPermanent(chat, *invite); + chat->session().api().inviteLinks().setMyPermanent(chat, *invite); } else { - chat->session().api().inviteLinks().clearPermanent(chat); + chat->session().api().inviteLinks().clearMyPermanent(chat); } if (const auto pinned = update.vpinned_msg_id()) { SetTopPinnedMessageId(chat, pinned->v);