From 819cd4a09912081f5121ca85fa89ab105ea957b2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 19 Jan 2021 14:26:00 +0400 Subject: [PATCH] Allow deleting revoked invite links. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/api/api_invite_links.cpp | 174 +++++++++++++++--- Telegram/SourceFiles/api/api_invite_links.h | 14 ++ .../boxes/peers/edit_peer_invite_links.cpp | 122 +++++++++++- Telegram/SourceFiles/info/info.style | 2 +- 5 files changed, 281 insertions(+), 33 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index fbe9396a3..1ea162bb2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1192,6 +1192,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_invite_context_revoke" = "Revoke"; "lng_group_invite_context_delete" = "Delete"; "lng_group_invite_context_delete_all" = "Delete all"; +"lng_group_invite_delete_sure" = "Are you sure you want to delete that revoked link?"; +"lng_group_invite_delete_all_sure" = "Are you sure you want to delete all revoked links? This action cannot be undone."; "lng_group_invite_revoked_title" = "Revoked links"; "lng_group_invite_revoke_about" = "Are you sure you want to revoke that invite link?"; "lng_group_invite_link_expired" = "Expired"; diff --git a/Telegram/SourceFiles/api/api_invite_links.cpp b/Telegram/SourceFiles/api/api_invite_links.cpp index a288d10a4..62b493fcc 100644 --- a/Telegram/SourceFiles/api/api_invite_links.cpp +++ b/Telegram/SourceFiles/api/api_invite_links.cpp @@ -122,25 +122,34 @@ auto InviteLinks::prepend( } auto &links = i->second; const auto permanent = lookupPermanent(links); - if (link.permanent) { - auto update = Update{ .peer = peer }; - if (permanent) { - update.was = permanent->link; - permanent->revoked = true; - } - editPermanentLink(peer, link.link); - if (permanent) { - update.now = *permanent; - _updates.fire(std::move(update)); + const auto hadPermanent = (permanent != nullptr); + auto updateOldPermanent = Update{ .peer = peer }; + if (link.permanent && hadPermanent) { + updateOldPermanent.was = permanent->link; + updateOldPermanent.now = *permanent; + updateOldPermanent.now->revoked = true; + links.links.erase(begin(links.links)); + if (links.count > 0) { + --links.count; } } + // Must not dereference 'permanent' pointer after that. + ++links.count; - if (permanent && !link.permanent) { + if (hadPermanent && !link.permanent) { links.links.insert(begin(links.links) + 1, link); } else { links.links.insert(begin(links.links), link); } + + if (link.permanent) { + editPermanentLink(peer, link.link); + } notify(peer); + + if (updateOldPermanent.now) { + _updates.fire(std::move(updateOldPermanent)); + } _updates.fire(Update{ .peer = peer, .now = link }); return link; } @@ -162,7 +171,10 @@ void InviteLinks::performEdit( TimeId expireDate, int usageLimit) { const auto key = LinkKey{ peer, link }; - if (const auto i = _editCallbacks.find(key); i != end(_editCallbacks)) { + if (_deleteCallbacks.contains(key)) { + return; + } else if (const auto i = _editCallbacks.find(key) + ; i != end(_editCallbacks)) { if (done) { i->second.push_back(std::move(done)); } @@ -184,7 +196,7 @@ void InviteLinks::performEdit( } using Flag = MTPmessages_EditExportedChatInvite::Flag; - const auto requestId = _api->request(MTPmessages_EditExportedChatInvite( + _api->request(MTPmessages_EditExportedChatInvite( MTP_flags((revoke ? Flag::f_revoked : Flag(0)) | ((!revoke && expireDate) ? Flag::f_expire_date : Flag(0)) | ((!revoke && usageLimit) ? Flag::f_usage_limit : Flag(0))), @@ -205,8 +217,14 @@ void InviteLinks::performEdit( key.link, &Link::link); if (j != end(i->second.links)) { - *j = link; - notify(peer); + if (link.revoked && !j->revoked) { + i->second.links.erase(j); + if (i->second.count > 0) { + --i->second.count; + } + } else { + *j = link; + } } } for (const auto &callback : *callbacks) { @@ -215,7 +233,7 @@ void InviteLinks::performEdit( _updates.fire(Update{ .peer = peer, .was = key.link, - .now = link + .now = link, }); }); }).fail([=](const RPCError &error) { @@ -236,6 +254,72 @@ void InviteLinks::revokePermanent( performCreate(peer, std::move(done), true); } +void InviteLinks::destroy( + not_null peer, + const QString &link, + Fn done) { + const auto key = LinkKey{ peer, link }; + + if (const auto i = _deleteCallbacks.find(key) + ; i != end(_deleteCallbacks)) { + if (done) { + i->second.push_back(std::move(done)); + } + return; + } + + auto &callbacks = _deleteCallbacks[key]; + if (done) { + callbacks.push_back(std::move(done)); + } + + _api->request(MTPmessages_DeleteExportedChatInvite( + peer->input, + MTP_string(link) + )).done([=](const MTPBool &result) { + const auto callbacks = _deleteCallbacks.take(key); + if (callbacks) { + for (const auto &callback : *callbacks) { + callback(); + } + } + _updates.fire(Update{ + .peer = peer, + .was = key.link, + }); + }).fail([=](const RPCError &error) { + _deleteCallbacks.erase(key); + }).send(); +} + +void InviteLinks::destroyAllRevoked( + not_null peer, + Fn done) { + if (const auto i = _deleteRevokedCallbacks.find(peer) + ; i != end(_deleteRevokedCallbacks)) { + if (done) { + i->second.push_back(std::move(done)); + } + return; + } + auto &callbacks = _deleteRevokedCallbacks[peer]; + if (done) { + callbacks.push_back(std::move(done)); + } + + _api->request(MTPmessages_DeleteRevokedExportedChatInvites( + peer->input + )).done([=](const MTPBool &result) { + if (const auto callbacks = _deleteRevokedCallbacks.take(peer)) { + for (const auto &callback : *callbacks) { + callback(); + } + } + _allRevokedDestroyed.fire_copy(peer); + }).fail([=](const RPCError &error) { + }).send(); +} + void InviteLinks::requestLinks(not_null peer) { if (_firstSliceRequests.contains(peer)) { return; @@ -317,6 +401,15 @@ auto InviteLinks::updates( }); } +rpl::producer<> InviteLinks::allRevokedDestroyed( + not_null peer) const { + using namespace rpl::mappers; + return _allRevokedDestroyed.events( + ) | rpl::filter( + _1 == peer + ) | rpl::to_empty; +} + void InviteLinks::requestJoinedFirstSlice(LinkKey key) { if (_firstJoinedRequests.contains(key)) { return; @@ -351,26 +444,63 @@ void InviteLinks::setPermanent( i = _firstSlices.emplace(peer).first; } auto &links = i->second; + auto updateOldPermanent = Update{ .peer = peer }; if (const auto permanent = lookupPermanent(links)) { if (permanent->link == link.link) { if (permanent->usage != link.usage) { permanent->usage = link.usage; - notify(peer); + _updates.fire(Update{ + .peer = peer, + .was = link.link, + .now = *permanent + }); } return; } - permanent->revoked = true; + updateOldPermanent.was = permanent->link; + updateOldPermanent.now = *permanent; + updateOldPermanent.now->revoked = true; + links.links.erase(begin(links.links)); + if (links.count > 0) { + --links.count; + } } links.links.insert(begin(links.links), link); + editPermanentLink(peer, link.link); notify(peer); + + if (updateOldPermanent.now) { + _updates.fire(std::move(updateOldPermanent)); + } + _updates.fire(Update{ .peer = peer, .now = link }); } void InviteLinks::clearPermanent(not_null peer) { - if (const auto permanent = lookupPermanent(peer)) { - permanent->revoked = true; - editPermanentLink(peer, QString()); - notify(peer); + auto i = _firstSlices.find(peer); + if (i == end(_firstSlices)) { + return; + } + auto &links = i->second; + const auto permanent = lookupPermanent(links); + if (!permanent) { + return; + } + + auto updateOldPermanent = Update{ .peer = peer }; + updateOldPermanent.was = permanent->link; + updateOldPermanent.now = *permanent; + updateOldPermanent.now->revoked = true; + links.links.erase(begin(links.links)); + if (links.count > 0) { + --links.count; + } + + editPermanentLink(peer, QString()); + notify(peer); + + if (updateOldPermanent.now) { + _updates.fire(std::move(updateOldPermanent)); } } diff --git a/Telegram/SourceFiles/api/api_invite_links.h b/Telegram/SourceFiles/api/api_invite_links.h index c3bc5a97c..72deca709 100644 --- a/Telegram/SourceFiles/api/api_invite_links.h +++ b/Telegram/SourceFiles/api/api_invite_links.h @@ -70,6 +70,13 @@ public: void revokePermanent( not_null peer, Fn done = nullptr); + void destroy( + not_null peer, + const QString &link, + Fn done = nullptr); + void destroyAllRevoked( + not_null peer, + Fn done = nullptr); void setPermanent( not_null peer, @@ -85,6 +92,8 @@ public: int fullCount); [[nodiscard]] rpl::producer updates( not_null peer) const; + [[nodiscard]] rpl::producer<> allRevokedDestroyed( + not_null peer) const; void requestMoreLinks( not_null peer, @@ -159,8 +168,13 @@ private: not_null, std::vector>> _createCallbacks; base::flat_map>> _editCallbacks; + base::flat_map>> _deleteCallbacks; + base::flat_map< + not_null, + std::vector>> _deleteRevokedCallbacks; rpl::event_stream _updates; + rpl::event_stream> _allRevokedDestroyed; }; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp index fede1dd8d..081538627 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp @@ -311,6 +311,36 @@ void EditLink(not_null peer, const InviteLinkData &data) { Ui::LayerOption::KeepOther); } +void DeleteLink(not_null peer, const QString &link) { + const auto box = std::make_shared>(); + const auto sure = [=] { + const auto finish = [=] { + if (*box) { + (*box)->closeBox(); + } + }; + peer->session().api().inviteLinks().destroy(peer, link, finish); + }; + *box = Ui::show( + Box(tr::lng_group_invite_delete_sure(tr::now), sure), + Ui::LayerOption::KeepOther); +} + +void DeleteAllRevoked(not_null peer) { + const auto box = std::make_shared>(); + const auto sure = [=] { + const auto finish = [=] { + if (*box) { + (*box)->closeBox(); + } + }; + peer->session().api().inviteLinks().destroyAllRevoked(peer, finish); + }; + *box = Ui::show( + Box(tr::lng_group_invite_delete_all_sure(tr::now), sure), + Ui::LayerOption::KeepOther); +} + not_null AddCreateLinkButton( not_null container) { const auto result = container->add( @@ -445,6 +475,7 @@ public: Controller(not_null peer, bool revoked); void prepare() override; + void loadMoreRows() override; void rowClicked(not_null row) override; void rowActionClicked(not_null row) override; base::unique_qptr rowContextMenu( @@ -467,6 +498,7 @@ private: void updateRow(const InviteLinkData &data, TimeId now); bool removeRow(const QString &link); + void appendSlice(const InviteLinksSlice &slice); void checkExpiringTimer(not_null row); void expiringProgressTimer(); @@ -474,10 +506,14 @@ private: QWidget *parent, not_null row); - not_null _peer; - bool _revoked = false; + const not_null _peer; + const bool _revoked = false; base::unique_qptr _menu; + QString _offsetLink; + bool _requesting = false; + bool _allLoaded = false; + base::flat_set> _expiringRows; base::Timer _updateExpiringTimer; @@ -501,8 +537,7 @@ Controller::Controller(not_null peer, bool revoked) peer ) | rpl::start_with_next([=](const Api::InviteLinkUpdate &update) { const auto now = base::unixtime::now(); - if (!update.now - || (!update.was.isEmpty() && update.now->revoked != _revoked)) { + if (!update.now || update.now->revoked != _revoked) { if (removeRow(update.was)) { delegate()->peerListRefreshRows(); } @@ -513,15 +548,63 @@ Controller::Controller(not_null peer, bool revoked) updateRow(*update.now, now); } }, _lifetime); + + if (_revoked) { + peer->session().api().inviteLinks().allRevokedDestroyed( + peer + ) | rpl::start_with_next([=] { + _requesting = false; + _allLoaded = true; + while (delegate()->peerListFullRowsCount()) { + delegate()->peerListRemoveRow(delegate()->peerListRowAt(0)); + } + delegate()->peerListRefreshRows(); + }, _lifetime); + } } void Controller::prepare() { + if (!_revoked) { + appendSlice(_peer->session().api().inviteLinks().links(_peer)); + } + if (!delegate()->peerListFullRowsCount()) { + loadMoreRows(); + } +} + +void Controller::loadMoreRows() { + if (_requesting || _allLoaded) { + return; + } + _requesting = true; + const auto done = [=](const InviteLinksSlice &slice) { + if (!_requesting) { + return; + } + _requesting = false; + if (slice.links.empty()) { + _allLoaded = true; + return; + } + appendSlice(slice); + }; + _peer->session().api().inviteLinks().requestMoreLinks( + _peer, + _offsetLink, + _revoked, + crl::guard(this, done)); +} + +void Controller::appendSlice(const InviteLinksSlice &slice) { const auto now = base::unixtime::now(); - const auto &links = _peer->session().api().inviteLinks().links(_peer); - for (const auto &link : links.links) { + for (const auto &link : slice.links) { if (!link.permanent || link.revoked) { appendRow(link, now); } + _offsetLink = link.link; + } + if (slice.links.size() >= slice.count) { + _allLoaded = true; } delegate()->peerListRefreshRows(); } @@ -559,9 +642,9 @@ base::unique_qptr Controller::createRowContextMenu( const auto link = data.link; auto result = base::make_unique_q(parent); if (data.revoked) { - //result->addAction(tr::lng_group_invite_context_delete(tr::now), [=] { - // // #TODO links delete - //}); + result->addAction(tr::lng_group_invite_context_delete(tr::now), [=] { + DeleteLink(_peer, link); + }); } else { result->addAction(tr::lng_group_invite_context_copy(tr::now), [=] { CopyLink(link); @@ -576,7 +659,6 @@ base::unique_qptr Controller::createRowContextMenu( const auto box = std::make_shared>(); const auto revoke = crl::guard(this, [=] { const auto done = crl::guard(this, [=](InviteLinkData data) { - // #TODO links add to revoked, remove from list if (*box) { (*box)->closeBox(); } @@ -946,6 +1028,23 @@ void ManageInviteLinksBox( st::inviteLinkRevokedTitlePadding)); const auto revoked = AddLinksList(container, peer, true); + const auto deleteAll = Ui::CreateChild( + container.get(), + tr::lng_group_invite_context_delete_all(tr::now), + st::boxLinkButton); + rpl::combine( + header->topValue(), + container->widthValue() + ) | rpl::start_with_next([=](int top, int outerWidth) { + deleteAll->moveToRight( + st::inviteLinkRevokedTitlePadding.left(), + top + st::inviteLinkRevokedTitlePadding.top(), + outerWidth); + }, deleteAll->lifetime()); + deleteAll->setClickedCallback([=] { + DeleteAllRevoked(peer); + }); + rpl::combine( list->heightValue(), revoked->heightValue() @@ -953,5 +1052,8 @@ void ManageInviteLinksBox( dividerAbout->toggle(!list, anim::type::instant); divider->toggle(list > 0 && revoked > 0, anim::type::instant); header->toggle(revoked > 0, anim::type::instant); + deleteAll->setVisible(revoked > 0); }, header->lifetime()); + + box->addButton(tr::lng_about_done(), [=] { box->closeBox(); }); } diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index f02207cda..f919fd15f 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -916,6 +916,6 @@ inviteLinkList: PeerList(defaultPeerList) { inviteLinkIconSkip: 7px; inviteLinkIconStroke: 2px; inviteLinkIcon: icon {{ "info/edit/links_link", mediaviewFileExtFg }}; -inviteLinkThreeDotsSkip: 8px; +inviteLinkThreeDotsSkip: 12px; inviteLinkRevokedTitlePadding: margins(22px, 16px, 10px, 9px); inviteLinkLimitMargin: margins(22px, 8px, 22px, 8px);