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_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";

View File

@ -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<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) {
if (_firstSliceRequests.contains(peer)) {
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) {
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<PeerData*> 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));
}
}

View File

@ -70,6 +70,13 @@ public:
void revokePermanent(
not_null<PeerData*> peer,
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(
not_null<PeerData*> peer,
@ -85,6 +92,8 @@ public:
int fullCount);
[[nodiscard]] rpl::producer<Update> updates(
not_null<PeerData*> peer) const;
[[nodiscard]] rpl::producer<> allRevokedDestroyed(
not_null<PeerData*> peer) const;
void requestMoreLinks(
not_null<PeerData*> peer,
@ -159,8 +168,13 @@ private:
not_null<PeerData*>,
std::vector<Fn<void(Link)>>> _createCallbacks;
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<not_null<PeerData*>> _allRevokedDestroyed;
};

View File

@ -311,6 +311,36 @@ void EditLink(not_null<PeerData*> peer, const InviteLinkData &data) {
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::VerticalLayout*> container) {
const auto result = container->add(
@ -445,6 +475,7 @@ public:
Controller(not_null<PeerData*> peer, bool revoked);
void prepare() override;
void loadMoreRows() override;
void rowClicked(not_null<PeerListRow*> row) override;
void rowActionClicked(not_null<PeerListRow*> row) override;
base::unique_qptr<Ui::PopupMenu> 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*> row);
void expiringProgressTimer();
@ -474,10 +506,14 @@ private:
QWidget *parent,
not_null<PeerListRow*> row);
not_null<PeerData*> _peer;
bool _revoked = false;
const not_null<PeerData*> _peer;
const bool _revoked = false;
base::unique_qptr<Ui::PopupMenu> _menu;
QString _offsetLink;
bool _requesting = false;
bool _allLoaded = false;
base::flat_set<not_null<Row*>> _expiringRows;
base::Timer _updateExpiringTimer;
@ -501,8 +537,7 @@ Controller::Controller(not_null<PeerData*> 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<PeerData*> 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<Ui::PopupMenu> Controller::createRowContextMenu(
const auto link = data.link;
auto result = base::make_unique_q<Ui::PopupMenu>(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<Ui::PopupMenu> Controller::createRowContextMenu(
const auto box = std::make_shared<QPointer<ConfirmBox>>();
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<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(
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(); });
}

View File

@ -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);