mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-17 22:57:11 +02:00
Allow to accept / reject requests by link.
This commit is contained in:
parent
9e05e44a14
commit
b4895ef730
5 changed files with 349 additions and 19 deletions
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "main/main_session.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Api {
|
||||
|
@ -435,6 +436,77 @@ void InviteLinks::requestMyLinks(not_null<PeerData*> peer) {
|
|||
_firstSliceRequests.emplace(peer, requestId);
|
||||
}
|
||||
|
||||
void InviteLinks::processRequest(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &link,
|
||||
not_null<UserData*> user,
|
||||
bool approved,
|
||||
Fn<void()> done,
|
||||
Fn<void()> fail) {
|
||||
if (_processRequests.contains({ peer, user })) {
|
||||
return;
|
||||
}
|
||||
_processRequests.emplace(
|
||||
std::pair{ peer, user },
|
||||
ProcessRequest{ std::move(done), std::move(fail) });
|
||||
using Flag = MTPmessages_HideChatJoinRequest::Flag;
|
||||
_api->request(MTPmessages_HideChatJoinRequest(
|
||||
MTP_flags(approved ? Flag::f_approved : Flag(0)),
|
||||
peer->input,
|
||||
user->inputUser
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
if (const auto chat = peer->asChat()) {
|
||||
if (chat->count > 0) {
|
||||
if (chat->participants.size() >= chat->count) {
|
||||
chat->participants.emplace(user);
|
||||
}
|
||||
++chat->count;
|
||||
}
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
_api->requestParticipantsCountDelayed(channel);
|
||||
}
|
||||
_api->applyUpdates(result);
|
||||
if (approved) {
|
||||
const auto i = _firstJoined.find({ peer, link });
|
||||
if (i != end(_firstJoined)) {
|
||||
++i->second.count;
|
||||
i->second.users.insert(
|
||||
begin(i->second.users),
|
||||
JoinedByLinkUser{ user, base::unixtime::now() });
|
||||
}
|
||||
}
|
||||
if (const auto callbacks = _processRequests.take({ peer, user })) {
|
||||
if (const auto &done = callbacks->done) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (const auto callbacks = _processRequests.take({ peer, user })) {
|
||||
if (const auto &fail = callbacks->fail) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
void InviteLinks::applyExternalUpdate(
|
||||
not_null<PeerData*> peer,
|
||||
InviteLink updated) {
|
||||
if (const auto i = _firstSlices.find(peer); i != end(_firstSlices)) {
|
||||
for (auto &link : i->second.links) {
|
||||
if (link.link == updated.link) {
|
||||
link = updated;
|
||||
}
|
||||
}
|
||||
}
|
||||
_updates.fire({
|
||||
.peer = peer,
|
||||
.admin = updated.admin,
|
||||
.was = updated.link,
|
||||
.now = updated,
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<JoinedByLinkSlice> InviteLinks::lookupJoinedFirstSlice(
|
||||
LinkKey key) const {
|
||||
const auto i = _firstJoined.find(key);
|
||||
|
@ -498,7 +570,7 @@ void InviteLinks::requestJoinedFirstSlice(LinkKey key) {
|
|||
return;
|
||||
}
|
||||
const auto requestId = _api->request(MTPmessages_GetChatInviteImporters(
|
||||
MTP_flags(0),
|
||||
MTP_flags(MTPmessages_GetChatInviteImporters::Flag::f_link),
|
||||
key.peer->input,
|
||||
MTP_string(key.link),
|
||||
MTPstring(), // q
|
||||
|
@ -645,6 +717,7 @@ auto InviteLinks::parse(
|
|||
.expireDate = data.vexpire_date().value_or_empty(),
|
||||
.usageLimit = data.vusage_limit().value_or_empty(),
|
||||
.usage = data.vusage().value_or_empty(),
|
||||
.requested = data.vrequested().value_or_empty(),
|
||||
.requestApproval = data.is_request_needed(),
|
||||
.permanent = data.is_permanent(),
|
||||
.revoked = data.is_revoked(),
|
||||
|
|
|
@ -19,6 +19,7 @@ struct InviteLink {
|
|||
TimeId expireDate = 0;
|
||||
int usageLimit = 0;
|
||||
int usage = 0;
|
||||
int requested = 0;
|
||||
bool requestApproval = false;
|
||||
bool permanent = false;
|
||||
bool revoked = false;
|
||||
|
@ -100,6 +101,15 @@ public:
|
|||
void requestMyLinks(not_null<PeerData*> peer);
|
||||
[[nodiscard]] const Links &myLinks(not_null<PeerData*> peer) const;
|
||||
|
||||
void processRequest(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &link,
|
||||
not_null<UserData*> user,
|
||||
bool approved,
|
||||
Fn<void()> done,
|
||||
Fn<void()> fail);
|
||||
void applyExternalUpdate(not_null<PeerData*> peer, InviteLink updated);
|
||||
|
||||
[[nodiscard]] rpl::producer<JoinedByLinkSlice> joinedFirstSliceValue(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &link,
|
||||
|
@ -136,6 +146,10 @@ private:
|
|||
return (a.peer == b.peer) && (a.link == b.link);
|
||||
}
|
||||
};
|
||||
struct ProcessRequest {
|
||||
Fn<void()> done;
|
||||
Fn<void()> fail;
|
||||
};
|
||||
|
||||
[[nodiscard]] Links parseSlice(
|
||||
not_null<PeerData*> peer,
|
||||
|
@ -199,6 +213,10 @@ private:
|
|||
not_null<PeerData*>,
|
||||
std::vector<Fn<void()>>> _deleteRevokedCallbacks;
|
||||
|
||||
base::flat_map<
|
||||
std::pair<not_null<PeerData*>, not_null<UserData*>>,
|
||||
ProcessRequest> _processRequests;
|
||||
|
||||
rpl::event_stream<Update> _updates;
|
||||
|
||||
struct AllRevokedDestroyed {
|
||||
|
|
|
@ -57,30 +57,109 @@ constexpr auto kShareQrPadding = 16;
|
|||
|
||||
using LinkData = Api::InviteLink;
|
||||
|
||||
class Controller final : public PeerListController {
|
||||
class RequestedRow final : public PeerListRow {
|
||||
public:
|
||||
explicit RequestedRow(not_null<PeerData*> peer);
|
||||
|
||||
QSize actionSize() const override;
|
||||
QMargins actionMargins() const override;
|
||||
void paintAction(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) override;
|
||||
|
||||
};
|
||||
|
||||
RequestedRow::RequestedRow(not_null<PeerData*> peer)
|
||||
: PeerListRow(peer) {
|
||||
}
|
||||
|
||||
QSize RequestedRow::actionSize() const {
|
||||
return QSize(
|
||||
st::inviteLinkThreeDotsIcon.width(),
|
||||
st::inviteLinkThreeDotsIcon.height());
|
||||
}
|
||||
|
||||
QMargins RequestedRow::actionMargins() const {
|
||||
return QMargins(
|
||||
0,
|
||||
(st::inviteLinkList.item.height - actionSize().height()) / 2,
|
||||
st::inviteLinkThreeDotsSkip,
|
||||
0);
|
||||
}
|
||||
|
||||
void RequestedRow::paintAction(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) {
|
||||
(actionSelected
|
||||
? st::inviteLinkThreeDotsIconOver
|
||||
: st::inviteLinkThreeDotsIcon).paint(p, x, y, outerWidth);
|
||||
}
|
||||
|
||||
class Controller final
|
||||
: public PeerListController
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
enum class Role {
|
||||
Requested,
|
||||
Joined,
|
||||
};
|
||||
Controller(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> admin,
|
||||
rpl::producer<LinkData> data);
|
||||
rpl::producer<LinkData> data,
|
||||
Role role);
|
||||
|
||||
void prepare() override;
|
||||
void loadMoreRows() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void rowActionClicked(not_null<PeerListRow*> row) override;
|
||||
Main::Session &session() const override;
|
||||
|
||||
rpl::producer<int> boxHeightValue() const override;
|
||||
int descriptionTopSkipMin() const override;
|
||||
|
||||
struct Processed {
|
||||
not_null<UserData*> user;
|
||||
bool approved = false;
|
||||
};
|
||||
[[nodiscard]] rpl::producer<Processed> processed() const {
|
||||
return _processed.events();
|
||||
}
|
||||
|
||||
private:
|
||||
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) override;
|
||||
|
||||
void setupAboveJoinedWidget();
|
||||
void appendSlice(const Api::JoinedByLinkSlice &slice);
|
||||
void addHeaderBlock(not_null<Ui::VerticalLayout*> container);
|
||||
not_null<Ui::SlideWrap<>*> addRequestedListBlock(
|
||||
not_null<Ui::VerticalLayout*> container);
|
||||
void updateWithProcessed(Processed processed);
|
||||
|
||||
[[nodiscard]] rpl::producer<LinkData> dataValue() const;
|
||||
|
||||
[[nodiscard]] base::unique_qptr<Ui::PopupMenu> createRowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row);
|
||||
void processRequest(not_null<UserData*> user, bool approved);
|
||||
|
||||
const not_null<PeerData*> _peer;
|
||||
const Role _role = Role::Joined;
|
||||
rpl::variable<LinkData> _data;
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
rpl::event_stream<Processed> _processed;
|
||||
|
||||
QString _link;
|
||||
bool _revoked = false;
|
||||
|
||||
|
@ -220,10 +299,12 @@ void QrBox(
|
|||
Controller::Controller(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> admin,
|
||||
rpl::producer<LinkData> data)
|
||||
rpl::producer<LinkData> data,
|
||||
Role role)
|
||||
: _peer(peer)
|
||||
, _role(role)
|
||||
, _data(LinkData{ .admin = admin })
|
||||
, _api(&_peer->session().api().instance()) {
|
||||
, _api(&session().api().instance()) {
|
||||
_data = std::move(data);
|
||||
const auto current = _data.current();
|
||||
_link = current.link;
|
||||
|
@ -386,7 +467,87 @@ void Controller::addHeaderBlock(not_null<Ui::VerticalLayout*> container) {
|
|||
}, lifetime());
|
||||
}
|
||||
|
||||
not_null<Ui::SlideWrap<>*> Controller::addRequestedListBlock(
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
using namespace Settings;
|
||||
|
||||
auto result = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(
|
||||
container)));
|
||||
const auto wrap = result->entity();
|
||||
// Make this container occupy full width.
|
||||
wrap->add(object_ptr<Ui::RpWidget>(wrap));
|
||||
AddDivider(wrap);
|
||||
AddSkip(wrap);
|
||||
auto requestedCount = dataValue(
|
||||
) | rpl::filter([](const LinkData &data) {
|
||||
return data.requested > 0;
|
||||
}) | rpl::map([=](const LinkData &data) {
|
||||
return float64(data.requested);
|
||||
});
|
||||
AddSubsectionTitle(
|
||||
wrap,
|
||||
tr::lng_group_invite_requested_full(
|
||||
lt_count_decimal,
|
||||
std::move(requestedCount)));
|
||||
|
||||
const auto delegate = container->lifetime().make_state<
|
||||
PeerListContentDelegateSimple
|
||||
>();
|
||||
const auto controller = container->lifetime().make_state<
|
||||
Controller
|
||||
>(_peer, _data.current().admin, _data.value(), Role::Requested);
|
||||
const auto content = container->add(object_ptr<PeerListContent>(
|
||||
container,
|
||||
controller));
|
||||
delegate->setContent(content);
|
||||
controller->setDelegate(delegate);
|
||||
|
||||
controller->processed(
|
||||
) | rpl::start_with_next([=](Processed processed) {
|
||||
updateWithProcessed(processed);
|
||||
}, lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Controller::prepare() {
|
||||
if (_role == Role::Joined) {
|
||||
setupAboveJoinedWidget();
|
||||
|
||||
_allLoaded = (_data.current().usage == 0);
|
||||
|
||||
const auto &inviteLinks = session().api().inviteLinks();
|
||||
const auto slice = inviteLinks.joinedFirstSliceLoaded(_peer, _link);
|
||||
if (slice) {
|
||||
appendSlice(*slice);
|
||||
}
|
||||
} else {
|
||||
_allLoaded = (_data.current().requested == 0);
|
||||
}
|
||||
loadMoreRows();
|
||||
}
|
||||
|
||||
void Controller::updateWithProcessed(Processed processed) {
|
||||
const auto user = processed.user;
|
||||
auto updated = _data.current();
|
||||
if (processed.approved) {
|
||||
++updated.usage;
|
||||
if (!delegate()->peerListFindRow(user->id.value)) {
|
||||
delegate()->peerListPrependRow(
|
||||
std::make_unique<PeerListRow>(user));
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
}
|
||||
if (updated.requested > 0) {
|
||||
--updated.requested;
|
||||
}
|
||||
session().api().inviteLinks().applyExternalUpdate(_peer, updated);
|
||||
}
|
||||
|
||||
void Controller::setupAboveJoinedWidget() {
|
||||
using namespace Settings;
|
||||
|
||||
auto header = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
|
||||
|
@ -406,6 +567,8 @@ void Controller::prepare() {
|
|||
rpl::single(langDateTime(base::unixtime::parse(current.date))));
|
||||
AddSkip(container, st::membersMarginBottom);
|
||||
|
||||
auto requestedWrap = addRequestedListBlock(container);
|
||||
|
||||
const auto listHeaderWrap = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
|
@ -484,6 +647,8 @@ void Controller::prepare() {
|
|||
} else {
|
||||
remaining->show();
|
||||
}
|
||||
|
||||
requestedWrap->toggle(data.requested > 0, anim::type::instant);
|
||||
}, remaining->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
|
@ -500,22 +665,16 @@ void Controller::prepare() {
|
|||
_headerWidget = header.data();
|
||||
|
||||
delegate()->peerListSetAboveWidget(std::move(header));
|
||||
_allLoaded = (current.usage == 0);
|
||||
|
||||
const auto &inviteLinks = _peer->session().api().inviteLinks();
|
||||
const auto slice = inviteLinks.joinedFirstSliceLoaded(_peer, _link);
|
||||
if (slice) {
|
||||
appendSlice(*slice);
|
||||
}
|
||||
loadMoreRows();
|
||||
}
|
||||
|
||||
void Controller::loadMoreRows() {
|
||||
if (_requestId || _allLoaded) {
|
||||
return;
|
||||
}
|
||||
using Flag = MTPmessages_GetChatInviteImporters::Flag;
|
||||
_requestId = _api.request(MTPmessages_GetChatInviteImporters(
|
||||
MTP_flags(0),
|
||||
MTP_flags(Flag::f_link
|
||||
| (_role == Role::Requested ? Flag::f_requested : Flag(0))),
|
||||
_peer->input,
|
||||
MTP_string(_link),
|
||||
MTPstring(), // q
|
||||
|
@ -536,8 +695,9 @@ void Controller::loadMoreRows() {
|
|||
void Controller::appendSlice(const Api::JoinedByLinkSlice &slice) {
|
||||
for (const auto &user : slice.users) {
|
||||
_lastUser = user;
|
||||
delegate()->peerListAppendRow(
|
||||
std::make_unique<PeerListRow>(user.user));
|
||||
delegate()->peerListAppendRow((_role == Role::Requested)
|
||||
? std::make_unique<RequestedRow>(user.user)
|
||||
: std::make_unique<PeerListRow>(user.user));
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
if (delegate()->peerListFullRowsCount() > 0) {
|
||||
|
@ -552,6 +712,71 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
|
|||
Ui::showPeerProfile(row->peer());
|
||||
}
|
||||
|
||||
void Controller::rowActionClicked(not_null<PeerListRow*> row) {
|
||||
if (_role != Role::Requested) {
|
||||
return;
|
||||
}
|
||||
delegate()->peerListShowRowMenu(row, true);
|
||||
}
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> Controller::rowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) {
|
||||
auto result = createRowContextMenu(parent, row);
|
||||
|
||||
if (result) {
|
||||
// First clear _menu value, so that we don't check row positions yet.
|
||||
base::take(_menu);
|
||||
|
||||
// Here unique_qptr is used like a shared pointer, where
|
||||
// not the last destroyed pointer destroys the object, but the first.
|
||||
_menu = base::unique_qptr<Ui::PopupMenu>(result.get());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> Controller::createRowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) {
|
||||
const auto user = row->peer()->asUser();
|
||||
Assert(user != nullptr);
|
||||
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(parent);
|
||||
const auto add = _peer->isBroadcast()
|
||||
? tr::lng_group_requests_add_channel(tr::now)
|
||||
: tr::lng_group_requests_add(tr::now);
|
||||
result->addAction(add, [=] {
|
||||
processRequest(user, true);
|
||||
});
|
||||
result->addAction(tr::lng_group_requests_dismiss(tr::now), [=] {
|
||||
processRequest(user, false);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void Controller::processRequest(
|
||||
not_null<UserData*> user,
|
||||
bool approved) {
|
||||
const auto done = crl::guard(this, [=] {
|
||||
_processed.fire({ user, approved });
|
||||
if (const auto row = delegate()->peerListFindRow(user->id.value)) {
|
||||
delegate()->peerListRemoveRow(row);
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
});
|
||||
const auto fail = crl::guard(this, [=] {
|
||||
_processed.fire({ user, false });
|
||||
});
|
||||
session().api().inviteLinks().processRequest(
|
||||
_peer,
|
||||
_data.current().link,
|
||||
user,
|
||||
approved,
|
||||
done,
|
||||
fail);
|
||||
}
|
||||
|
||||
Main::Session &Controller::session() const {
|
||||
return _peer->session();
|
||||
}
|
||||
|
@ -1055,7 +1280,11 @@ void ShowInviteLinkBox(
|
|||
};
|
||||
Ui::show(
|
||||
Box<PeerListBox>(
|
||||
std::make_unique<Controller>(peer, link.admin, std::move(data)),
|
||||
std::make_unique<Controller>(
|
||||
peer,
|
||||
link.admin,
|
||||
std::move(data),
|
||||
Controller::Role::Joined),
|
||||
std::move(initBox)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
|
|
@ -178,6 +178,16 @@ private:
|
|||
tr::now,
|
||||
lt_count_decimal,
|
||||
link.usageLimit - link.usage);
|
||||
} else if (link.usage > 0 && link.requested > 0) {
|
||||
result += ", " + tr::lng_group_invite_requested(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
link.requested);
|
||||
} else if (link.requested > 0) {
|
||||
result = tr::lng_group_invite_requested_full(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
link.requested);
|
||||
}
|
||||
if (link.expireDate > now) {
|
||||
const auto left = (link.expireDate - now);
|
||||
|
|
|
@ -309,8 +309,8 @@ void CreateInviteLinkBox(
|
|||
bool isGroup,
|
||||
Fn<void(InviteLinkFields)> done) {
|
||||
EditInviteLinkBox(
|
||||
box,
|
||||
InviteLinkFields{ .isGroup = isGroup },
|
||||
box,
|
||||
InviteLinkFields{ .isGroup = isGroup },
|
||||
std::move(done));
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue