Allow deleting revoked invite links.

This commit is contained in:
John Preston 2021-01-19 14:26:00 +04:00
parent 144bad6c74
commit 819cd4a099
5 changed files with 281 additions and 33 deletions

View file

@ -1192,6 +1192,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_invite_context_revoke" = "Revoke"; "lng_group_invite_context_revoke" = "Revoke";
"lng_group_invite_context_delete" = "Delete"; "lng_group_invite_context_delete" = "Delete";
"lng_group_invite_context_delete_all" = "Delete all"; "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_revoked_title" = "Revoked links";
"lng_group_invite_revoke_about" = "Are you sure you want to revoke that invite link?"; "lng_group_invite_revoke_about" = "Are you sure you want to revoke that invite link?";
"lng_group_invite_link_expired" = "Expired"; "lng_group_invite_link_expired" = "Expired";

View file

@ -122,25 +122,34 @@ auto InviteLinks::prepend(
} }
auto &links = i->second; auto &links = i->second;
const auto permanent = lookupPermanent(links); const auto permanent = lookupPermanent(links);
if (link.permanent) { const auto hadPermanent = (permanent != nullptr);
auto update = Update{ .peer = peer }; auto updateOldPermanent = Update{ .peer = peer };
if (permanent) { if (link.permanent && hadPermanent) {
update.was = permanent->link; updateOldPermanent.was = permanent->link;
permanent->revoked = true; updateOldPermanent.now = *permanent;
} updateOldPermanent.now->revoked = true;
editPermanentLink(peer, link.link); links.links.erase(begin(links.links));
if (permanent) { if (links.count > 0) {
update.now = *permanent; --links.count;
_updates.fire(std::move(update));
} }
} }
// Must not dereference 'permanent' pointer after that.
++links.count; ++links.count;
if (permanent && !link.permanent) { if (hadPermanent && !link.permanent) {
links.links.insert(begin(links.links) + 1, link); links.links.insert(begin(links.links) + 1, link);
} else { } else {
links.links.insert(begin(links.links), link); links.links.insert(begin(links.links), link);
} }
if (link.permanent) {
editPermanentLink(peer, link.link);
}
notify(peer); notify(peer);
if (updateOldPermanent.now) {
_updates.fire(std::move(updateOldPermanent));
}
_updates.fire(Update{ .peer = peer, .now = link }); _updates.fire(Update{ .peer = peer, .now = link });
return link; return link;
} }
@ -162,7 +171,10 @@ void InviteLinks::performEdit(
TimeId expireDate, TimeId expireDate,
int usageLimit) { int usageLimit) {
const auto key = LinkKey{ peer, link }; 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) { if (done) {
i->second.push_back(std::move(done)); i->second.push_back(std::move(done));
} }
@ -184,7 +196,7 @@ void InviteLinks::performEdit(
} }
using Flag = MTPmessages_EditExportedChatInvite::Flag; using Flag = MTPmessages_EditExportedChatInvite::Flag;
const auto requestId = _api->request(MTPmessages_EditExportedChatInvite( _api->request(MTPmessages_EditExportedChatInvite(
MTP_flags((revoke ? Flag::f_revoked : Flag(0)) MTP_flags((revoke ? Flag::f_revoked : Flag(0))
| ((!revoke && expireDate) ? Flag::f_expire_date : Flag(0)) | ((!revoke && expireDate) ? Flag::f_expire_date : Flag(0))
| ((!revoke && usageLimit) ? Flag::f_usage_limit : Flag(0))), | ((!revoke && usageLimit) ? Flag::f_usage_limit : Flag(0))),
@ -205,8 +217,14 @@ void InviteLinks::performEdit(
key.link, key.link,
&Link::link); &Link::link);
if (j != end(i->second.links)) { if (j != end(i->second.links)) {
*j = link; if (link.revoked && !j->revoked) {
notify(peer); i->second.links.erase(j);
if (i->second.count > 0) {
--i->second.count;
}
} else {
*j = link;
}
} }
} }
for (const auto &callback : *callbacks) { for (const auto &callback : *callbacks) {
@ -215,7 +233,7 @@ void InviteLinks::performEdit(
_updates.fire(Update{ _updates.fire(Update{
.peer = peer, .peer = peer,
.was = key.link, .was = key.link,
.now = link .now = link,
}); });
}); });
}).fail([=](const RPCError &error) { }).fail([=](const RPCError &error) {
@ -236,6 +254,72 @@ void InviteLinks::revokePermanent(
performCreate(peer, std::move(done), true); performCreate(peer, std::move(done), true);
} }
void InviteLinks::destroy(
not_null<PeerData*> peer,
const QString &link,
Fn<void()> 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<PeerData*> peer,
Fn<void()> 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<PeerData*> peer) { void InviteLinks::requestLinks(not_null<PeerData*> peer) {
if (_firstSliceRequests.contains(peer)) { if (_firstSliceRequests.contains(peer)) {
return; return;
@ -317,6 +401,15 @@ auto InviteLinks::updates(
}); });
} }
rpl::producer<> InviteLinks::allRevokedDestroyed(
not_null<PeerData*> peer) const {
using namespace rpl::mappers;
return _allRevokedDestroyed.events(
) | rpl::filter(
_1 == peer
) | rpl::to_empty;
}
void InviteLinks::requestJoinedFirstSlice(LinkKey key) { void InviteLinks::requestJoinedFirstSlice(LinkKey key) {
if (_firstJoinedRequests.contains(key)) { if (_firstJoinedRequests.contains(key)) {
return; return;
@ -351,26 +444,63 @@ void InviteLinks::setPermanent(
i = _firstSlices.emplace(peer).first; i = _firstSlices.emplace(peer).first;
} }
auto &links = i->second; auto &links = i->second;
auto updateOldPermanent = Update{ .peer = peer };
if (const auto permanent = lookupPermanent(links)) { if (const auto permanent = lookupPermanent(links)) {
if (permanent->link == link.link) { if (permanent->link == link.link) {
if (permanent->usage != link.usage) { if (permanent->usage != link.usage) {
permanent->usage = link.usage; permanent->usage = link.usage;
notify(peer); _updates.fire(Update{
.peer = peer,
.was = link.link,
.now = *permanent
});
} }
return; 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); links.links.insert(begin(links.links), link);
editPermanentLink(peer, link.link); editPermanentLink(peer, link.link);
notify(peer); notify(peer);
if (updateOldPermanent.now) {
_updates.fire(std::move(updateOldPermanent));
}
_updates.fire(Update{ .peer = peer, .now = link });
} }
void InviteLinks::clearPermanent(not_null<PeerData*> peer) { void InviteLinks::clearPermanent(not_null<PeerData*> peer) {
if (const auto permanent = lookupPermanent(peer)) { auto i = _firstSlices.find(peer);
permanent->revoked = true; if (i == end(_firstSlices)) {
editPermanentLink(peer, QString()); return;
notify(peer); }
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));
} }
} }

View file

@ -70,6 +70,13 @@ public:
void revokePermanent( void revokePermanent(
not_null<PeerData*> peer, not_null<PeerData*> peer,
Fn<void(Link)> done = nullptr); Fn<void(Link)> done = nullptr);
void destroy(
not_null<PeerData*> peer,
const QString &link,
Fn<void()> done = nullptr);
void destroyAllRevoked(
not_null<PeerData*> peer,
Fn<void()> done = nullptr);
void setPermanent( void setPermanent(
not_null<PeerData*> peer, not_null<PeerData*> peer,
@ -85,6 +92,8 @@ public:
int fullCount); int fullCount);
[[nodiscard]] rpl::producer<Update> updates( [[nodiscard]] rpl::producer<Update> updates(
not_null<PeerData*> peer) const; not_null<PeerData*> peer) const;
[[nodiscard]] rpl::producer<> allRevokedDestroyed(
not_null<PeerData*> peer) const;
void requestMoreLinks( void requestMoreLinks(
not_null<PeerData*> peer, not_null<PeerData*> peer,
@ -159,8 +168,13 @@ private:
not_null<PeerData*>, not_null<PeerData*>,
std::vector<Fn<void(Link)>>> _createCallbacks; std::vector<Fn<void(Link)>>> _createCallbacks;
base::flat_map<LinkKey, std::vector<Fn<void(Link)>>> _editCallbacks; base::flat_map<LinkKey, std::vector<Fn<void(Link)>>> _editCallbacks;
base::flat_map<LinkKey, std::vector<Fn<void()>>> _deleteCallbacks;
base::flat_map<
not_null<PeerData*>,
std::vector<Fn<void()>>> _deleteRevokedCallbacks;
rpl::event_stream<Update> _updates; rpl::event_stream<Update> _updates;
rpl::event_stream<not_null<PeerData*>> _allRevokedDestroyed;
}; };

View file

@ -311,6 +311,36 @@ void EditLink(not_null<PeerData*> peer, const InviteLinkData &data) {
Ui::LayerOption::KeepOther); Ui::LayerOption::KeepOther);
} }
void DeleteLink(not_null<PeerData*> peer, const QString &link) {
const auto box = std::make_shared<QPointer<ConfirmBox>>();
const auto sure = [=] {
const auto finish = [=] {
if (*box) {
(*box)->closeBox();
}
};
peer->session().api().inviteLinks().destroy(peer, link, finish);
};
*box = Ui::show(
Box<ConfirmBox>(tr::lng_group_invite_delete_sure(tr::now), sure),
Ui::LayerOption::KeepOther);
}
void DeleteAllRevoked(not_null<PeerData*> peer) {
const auto box = std::make_shared<QPointer<ConfirmBox>>();
const auto sure = [=] {
const auto finish = [=] {
if (*box) {
(*box)->closeBox();
}
};
peer->session().api().inviteLinks().destroyAllRevoked(peer, finish);
};
*box = Ui::show(
Box<ConfirmBox>(tr::lng_group_invite_delete_all_sure(tr::now), sure),
Ui::LayerOption::KeepOther);
}
not_null<Ui::SettingsButton*> AddCreateLinkButton( not_null<Ui::SettingsButton*> AddCreateLinkButton(
not_null<Ui::VerticalLayout*> container) { not_null<Ui::VerticalLayout*> container) {
const auto result = container->add( const auto result = container->add(
@ -445,6 +475,7 @@ public:
Controller(not_null<PeerData*> peer, bool revoked); Controller(not_null<PeerData*> peer, bool revoked);
void prepare() override; void prepare() override;
void loadMoreRows() override;
void rowClicked(not_null<PeerListRow*> row) override; void rowClicked(not_null<PeerListRow*> row) override;
void rowActionClicked(not_null<PeerListRow*> row) override; void rowActionClicked(not_null<PeerListRow*> row) override;
base::unique_qptr<Ui::PopupMenu> rowContextMenu( base::unique_qptr<Ui::PopupMenu> rowContextMenu(
@ -467,6 +498,7 @@ private:
void updateRow(const InviteLinkData &data, TimeId now); void updateRow(const InviteLinkData &data, TimeId now);
bool removeRow(const QString &link); bool removeRow(const QString &link);
void appendSlice(const InviteLinksSlice &slice);
void checkExpiringTimer(not_null<Row*> row); void checkExpiringTimer(not_null<Row*> row);
void expiringProgressTimer(); void expiringProgressTimer();
@ -474,10 +506,14 @@ private:
QWidget *parent, QWidget *parent,
not_null<PeerListRow*> row); not_null<PeerListRow*> row);
not_null<PeerData*> _peer; const not_null<PeerData*> _peer;
bool _revoked = false; const bool _revoked = false;
base::unique_qptr<Ui::PopupMenu> _menu; base::unique_qptr<Ui::PopupMenu> _menu;
QString _offsetLink;
bool _requesting = false;
bool _allLoaded = false;
base::flat_set<not_null<Row*>> _expiringRows; base::flat_set<not_null<Row*>> _expiringRows;
base::Timer _updateExpiringTimer; base::Timer _updateExpiringTimer;
@ -501,8 +537,7 @@ Controller::Controller(not_null<PeerData*> peer, bool revoked)
peer peer
) | rpl::start_with_next([=](const Api::InviteLinkUpdate &update) { ) | rpl::start_with_next([=](const Api::InviteLinkUpdate &update) {
const auto now = base::unixtime::now(); const auto now = base::unixtime::now();
if (!update.now if (!update.now || update.now->revoked != _revoked) {
|| (!update.was.isEmpty() && update.now->revoked != _revoked)) {
if (removeRow(update.was)) { if (removeRow(update.was)) {
delegate()->peerListRefreshRows(); delegate()->peerListRefreshRows();
} }
@ -513,15 +548,63 @@ Controller::Controller(not_null<PeerData*> peer, bool revoked)
updateRow(*update.now, now); updateRow(*update.now, now);
} }
}, _lifetime); }, _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() { 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 now = base::unixtime::now();
const auto &links = _peer->session().api().inviteLinks().links(_peer); for (const auto &link : slice.links) {
for (const auto &link : links.links) {
if (!link.permanent || link.revoked) { if (!link.permanent || link.revoked) {
appendRow(link, now); appendRow(link, now);
} }
_offsetLink = link.link;
}
if (slice.links.size() >= slice.count) {
_allLoaded = true;
} }
delegate()->peerListRefreshRows(); delegate()->peerListRefreshRows();
} }
@ -559,9 +642,9 @@ base::unique_qptr<Ui::PopupMenu> Controller::createRowContextMenu(
const auto link = data.link; const auto link = data.link;
auto result = base::make_unique_q<Ui::PopupMenu>(parent); auto result = base::make_unique_q<Ui::PopupMenu>(parent);
if (data.revoked) { if (data.revoked) {
//result->addAction(tr::lng_group_invite_context_delete(tr::now), [=] { result->addAction(tr::lng_group_invite_context_delete(tr::now), [=] {
// // #TODO links delete DeleteLink(_peer, link);
//}); });
} else { } else {
result->addAction(tr::lng_group_invite_context_copy(tr::now), [=] { result->addAction(tr::lng_group_invite_context_copy(tr::now), [=] {
CopyLink(link); CopyLink(link);
@ -576,7 +659,6 @@ base::unique_qptr<Ui::PopupMenu> Controller::createRowContextMenu(
const auto box = std::make_shared<QPointer<ConfirmBox>>(); const auto box = std::make_shared<QPointer<ConfirmBox>>();
const auto revoke = crl::guard(this, [=] { const auto revoke = crl::guard(this, [=] {
const auto done = crl::guard(this, [=](InviteLinkData data) { const auto done = crl::guard(this, [=](InviteLinkData data) {
// #TODO links add to revoked, remove from list
if (*box) { if (*box) {
(*box)->closeBox(); (*box)->closeBox();
} }
@ -946,6 +1028,23 @@ void ManageInviteLinksBox(
st::inviteLinkRevokedTitlePadding)); st::inviteLinkRevokedTitlePadding));
const auto revoked = AddLinksList(container, peer, true); const auto revoked = AddLinksList(container, peer, true);
const auto deleteAll = Ui::CreateChild<Ui::LinkButton>(
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( rpl::combine(
list->heightValue(), list->heightValue(),
revoked->heightValue() revoked->heightValue()
@ -953,5 +1052,8 @@ void ManageInviteLinksBox(
dividerAbout->toggle(!list, anim::type::instant); dividerAbout->toggle(!list, anim::type::instant);
divider->toggle(list > 0 && revoked > 0, anim::type::instant); divider->toggle(list > 0 && revoked > 0, anim::type::instant);
header->toggle(revoked > 0, anim::type::instant); header->toggle(revoked > 0, anim::type::instant);
deleteAll->setVisible(revoked > 0);
}, header->lifetime()); }, header->lifetime());
box->addButton(tr::lng_about_done(), [=] { box->closeBox(); });
} }

View file

@ -916,6 +916,6 @@ inviteLinkList: PeerList(defaultPeerList) {
inviteLinkIconSkip: 7px; inviteLinkIconSkip: 7px;
inviteLinkIconStroke: 2px; inviteLinkIconStroke: 2px;
inviteLinkIcon: icon {{ "info/edit/links_link", mediaviewFileExtFg }}; inviteLinkIcon: icon {{ "info/edit/links_link", mediaviewFileExtFg }};
inviteLinkThreeDotsSkip: 8px; inviteLinkThreeDotsSkip: 12px;
inviteLinkRevokedTitlePadding: margins(22px, 16px, 10px, 9px); inviteLinkRevokedTitlePadding: margins(22px, 16px, 10px, 9px);
inviteLinkLimitMargin: margins(22px, 8px, 22px, 8px); inviteLinkLimitMargin: margins(22px, 8px, 22px, 8px);