Add pending requests box for chats to be added.

This commit is contained in:
John Preston
2026-06-12 09:49:47 +04:00
parent 0ce83f64a2
commit 6c65aae5cc
6 changed files with 579 additions and 0 deletions
+2
View File
@@ -232,6 +232,8 @@ PRIVATE
boxes/peers/choose_peer_box.h
boxes/peers/community_box.cpp
boxes/peers/community_box.h
boxes/peers/community_pending_requests_box.cpp
boxes/peers/community_pending_requests_box.h
boxes/peers/create_managed_bot_box.cpp
boxes/peers/create_managed_bot_box.h
boxes/peers/edit_contact_box.cpp
+19
View File
@@ -2240,6 +2240,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_community_chats_viewable" = "Chats you can view";
"lng_community_chats_requestable" = "Chats you can request to join";
"lng_community_add_chat" = "Add a Chat to Community";
"lng_community_requests_title" = "Pending Requests";
"lng_community_requests_about" = "Chats require admin approval before they're added to the community.";
"lng_community_requests_count#one" = "{count} chat suggested for this community";
"lng_community_requests_count#other" = "{count} chats suggested for this community";
"lng_community_requests_button#one" = "{count} Pending Request";
"lng_community_requests_button#other" = "{count} Pending Requests";
"lng_community_request_suggested_group" = "{user} suggested this group";
"lng_community_request_suggested_channel" = "{user} suggested this channel";
"lng_community_request_only_members" = "Only visible to its members";
"lng_community_request_add" = "Add";
"lng_community_request_decline" = "Decline";
"lng_community_requests_add_all" = "Add All";
"lng_community_requests_decline_all" = "Decline All";
"lng_community_requests_add_all_title" = "Add All Chats";
"lng_community_requests_add_all_sure#one" = "Do you want to add {count} chat to the community?";
"lng_community_requests_add_all_sure#other" = "Do you want to add {count} chats to the community?";
"lng_community_requests_decline_all_title" = "Decline All";
"lng_community_requests_decline_all_sure#one" = "Do you want to decline {count} request to join the community?";
"lng_community_requests_decline_all_sure#other" = "Do you want to decline all {count} requests to join the community?";
"lng_manage_monoforum" = "Direct Messages";
"lng_manage_monoforum_off" = "Off";
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "boxes/peer_list_box.h"
#include "boxes/peers/add_to_community_box.h"
#include "boxes/peers/community_pending_requests_box.h"
#include "boxes/peers/manage_community_box.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
@@ -163,6 +164,32 @@ void CommunityBox(
Ui::AddSkip(container);
Ui::AddDividerText(container, tr::lng_community_show_as_one_about());
if (community->canManageLinkedPeers()) {
const auto wrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
const auto inner = wrap->entity();
Ui::AddSkip(inner);
auto count = Info::Profile::PendingRequestsCountValue(
community
) | rpl::start_spawning(wrap->lifetime());
Settings::AddButtonWithIcon(
inner,
tr::lng_community_requests_button(
lt_count,
rpl::duplicate(count) | tr::to_count()),
st::settingsButton,
{ &st::menuIconInvite }
)->addClickHandler([=] {
ShowCommunityPendingRequestsBox(navigation, community);
});
wrap->toggleOn(
std::move(count) | rpl::map(rpl::mappers::_1 > 0),
anim::type::instant);
wrap->finishAnimating();
}
auto chats = info->linkedPeersValue(
) | rpl::map([=] {
return PartitionChats(info);
@@ -0,0 +1,489 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peers/community_pending_requests_box.h"
#include "api/api_communities.h"
#include "apiwrap.h"
#include "boxes/peer_list_box.h"
#include "boxes/peers/manage_community_box.h"
#include "data/data_channel.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/boxes/confirm_box.h"
#include "ui/effects/ripple_animation.h"
#include "ui/painter.h"
#include "ui/round_rect.h"
#include "ui/text/text_utilities.h"
#include "ui/vertical_list.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
namespace {
constexpr auto kPerPage = 100;
constexpr auto kAcceptButton = 1;
constexpr auto kRejectButton = 2;
class RowDelegate {
public:
[[nodiscard]] virtual QSize rowAcceptButtonSize() = 0;
[[nodiscard]] virtual QSize rowRejectButtonSize() = 0;
virtual void rowPaintAccept(
Painter &p,
QRect geometry,
std::unique_ptr<Ui::RippleAnimation> &ripple,
int outerWidth,
bool over) = 0;
virtual void rowPaintReject(
Painter &p,
QRect geometry,
std::unique_ptr<Ui::RippleAnimation> &ripple,
int outerWidth,
bool over) = 0;
};
class Row final : public PeerListRow {
public:
Row(
not_null<RowDelegate*> delegate,
const Api::CommunityPeerRequest &request);
int elementsCount() const override;
QRect elementGeometry(int element, int outerWidth) const override;
bool elementDisabled(int element) const override;
bool elementOnlySelect(int element) const override;
void elementAddRipple(
int element,
QPoint point,
Fn<void()> updateCallback) override;
void elementsStopLastRipple() override;
void elementsPaint(
Painter &p,
int outerWidth,
bool selected,
int selectedElement) override;
private:
const not_null<RowDelegate*> _delegate;
std::unique_ptr<Ui::RippleAnimation> _acceptRipple;
std::unique_ptr<Ui::RippleAnimation> _rejectRipple;
};
Row::Row(
not_null<RowDelegate*> delegate,
const Api::CommunityPeerRequest &request)
: PeerListRow(request.peer)
, _delegate(delegate) {
const auto suggestedBy = request.requestedBy
? request.requestedBy->shortName()
: QString();
auto status = request.peer->isBroadcast()
? tr::lng_community_request_suggested_channel(
tr::now,
lt_user,
suggestedBy)
: tr::lng_community_request_suggested_group(
tr::now,
lt_user,
suggestedBy);
if (!request.visible) {
status += QString::fromUtf8(" \xE2\x80\xA2 ")
+ tr::lng_community_request_only_members(tr::now);
}
setCustomStatus(status);
}
int Row::elementsCount() const {
return 2;
}
QRect Row::elementGeometry(int element, int outerWidth) const {
switch (element) {
case kAcceptButton: {
const auto size = _delegate->rowAcceptButtonSize();
return QRect(st::requestAcceptPosition, size);
} break;
case kRejectButton: {
const auto accept = _delegate->rowAcceptButtonSize();
const auto size = _delegate->rowRejectButtonSize();
return QRect(
(st::requestAcceptPosition
+ QPoint(accept.width() + st::requestButtonsSkip, 0)),
size);
} break;
}
return QRect();
}
bool Row::elementDisabled(int element) const {
return false;
}
bool Row::elementOnlySelect(int element) const {
return true;
}
void Row::elementAddRipple(
int element,
QPoint point,
Fn<void()> updateCallback) {
const auto pointer = (element == kAcceptButton)
? &_acceptRipple
: (element == kRejectButton)
? &_rejectRipple
: nullptr;
if (!pointer) {
return;
}
auto &ripple = *pointer;
if (!ripple) {
auto mask = Ui::RippleAnimation::RoundRectMask(
(element == kAcceptButton
? _delegate->rowAcceptButtonSize()
: _delegate->rowRejectButtonSize()),
st::buttonRadius);
ripple = std::make_unique<Ui::RippleAnimation>(
(element == kAcceptButton
? st::requestsAcceptButton.ripple
: st::requestsRejectButton.ripple),
std::move(mask),
std::move(updateCallback));
}
ripple->add(point);
}
void Row::elementsStopLastRipple() {
if (_acceptRipple) {
_acceptRipple->lastStop();
}
if (_rejectRipple) {
_rejectRipple->lastStop();
}
}
void Row::elementsPaint(
Painter &p,
int outerWidth,
bool selected,
int selectedElement) {
const auto accept = elementGeometry(kAcceptButton, outerWidth);
const auto reject = elementGeometry(kRejectButton, outerWidth);
const auto over = [&](int element) {
return (selectedElement == element);
};
_delegate->rowPaintAccept(
p,
accept,
_acceptRipple,
outerWidth,
over(kAcceptButton));
_delegate->rowPaintReject(
p,
reject,
_rejectRipple,
outerWidth,
over(kRejectButton));
}
class Controller final
: public PeerListController
, public RowDelegate
, public base::has_weak_ptr {
public:
Controller(
not_null<Window::SessionNavigation*> navigation,
not_null<ChannelData*> community);
Main::Session &session() const override;
void prepare() override;
void loadMoreRows() override;
void rowClicked(not_null<PeerListRow*> row) override;
void rowElementClicked(
not_null<PeerListRow*> row,
int element) override;
QSize rowAcceptButtonSize() override;
QSize rowRejectButtonSize() override;
void rowPaintAccept(
Painter &p,
QRect geometry,
std::unique_ptr<Ui::RippleAnimation> &ripple,
int outerWidth,
bool over) override;
void rowPaintReject(
Painter &p,
QRect geometry,
std::unique_ptr<Ui::RippleAnimation> &ripple,
int outerWidth,
bool over) override;
private:
void paintButton(
Painter &p,
QRect geometry,
const style::RoundButton &st,
const Ui::RoundRect &rect,
const Ui::RoundRect &rectOver,
std::unique_ptr<Ui::RippleAnimation> &ripple,
const QString &text,
int textWidth,
int outerWidth,
bool over);
void process(not_null<PeerListRow*> row, bool reject);
const not_null<Window::SessionNavigation*> _navigation;
const not_null<ChannelData*> _community;
QString _offset;
bool _allLoaded = false;
bool _loading = false;
Ui::RoundRect _acceptRect;
Ui::RoundRect _acceptRectOver;
Ui::RoundRect _rejectRect;
Ui::RoundRect _rejectRectOver;
QString _acceptText;
QString _rejectText;
int _acceptTextWidth = 0;
int _rejectTextWidth = 0;
};
Controller::Controller(
not_null<Window::SessionNavigation*> navigation,
not_null<ChannelData*> community)
: _navigation(navigation)
, _community(community)
, _acceptRect(st::buttonRadius, st::requestsAcceptButton.textBg)
, _acceptRectOver(st::buttonRadius, st::requestsAcceptButton.textBgOver)
, _rejectRect(st::buttonRadius, st::requestsRejectButton.textBg)
, _rejectRectOver(st::buttonRadius, st::requestsRejectButton.textBgOver)
, _acceptText(tr::lng_community_request_add(tr::now))
, _rejectText(tr::lng_community_request_decline(tr::now))
, _acceptTextWidth(st::requestsAcceptButton.style.font->width(_acceptText))
, _rejectTextWidth(st::requestsRejectButton.style.font->width(_rejectText)) {
setStyleOverrides(&st::requestsBoxList);
}
Main::Session &Controller::session() const {
return _community->session();
}
void Controller::prepare() {
delegate()->peerListSetTitle(tr::lng_community_requests_title());
loadMoreRows();
}
void Controller::loadMoreRows() {
if (_allLoaded || _loading) {
return;
}
_loading = true;
session().api().communities().requestPeerLinkRequests(
_community,
_offset,
kPerPage,
crl::guard(this, [=](Api::CommunityPeerRequestsSlice slice) {
_loading = false;
for (const auto &request : slice.list) {
if (!delegate()->peerListFindRow(request.peer->id.value)) {
delegate()->peerListAppendRow(
std::make_unique<Row>(this, request));
}
}
delegate()->peerListRefreshRows();
_offset = slice.nextOffset;
if (_offset.isEmpty()) {
_allLoaded = true;
}
}));
}
void Controller::rowClicked(not_null<PeerListRow*> row) {
const auto peer = row->peer();
if (const auto window = _navigation->parentController()) {
window->showPeer(peer);
}
}
void Controller::rowElementClicked(
not_null<PeerListRow*> row,
int element) {
if (element == kAcceptButton) {
process(row, false);
} else if (element == kRejectButton) {
process(row, true);
}
}
void Controller::process(not_null<PeerListRow*> row, bool reject) {
const auto peer = row->peer();
const auto id = peer->id.value;
session().api().communities().togglePeerLinkRequestApproval(
_community,
peer,
reject,
crl::guard(this, [=] {
if (const auto row = delegate()->peerListFindRow(id)) {
delegate()->peerListRemoveRow(row);
delegate()->peerListRefreshRows();
}
}),
crl::guard(this, [=](const QString &error) {
delegate()->peerListUiShow()->showToast(error);
}));
}
QSize Controller::rowAcceptButtonSize() {
return QSize(
_acceptTextWidth + st::requestsAcceptButton.width,
st::requestsAcceptButton.height);
}
QSize Controller::rowRejectButtonSize() {
return QSize(
_rejectTextWidth + st::requestsRejectButton.width,
st::requestsRejectButton.height);
}
void Controller::rowPaintAccept(
Painter &p,
QRect geometry,
std::unique_ptr<Ui::RippleAnimation> &ripple,
int outerWidth,
bool over) {
paintButton(
p,
geometry,
st::requestsAcceptButton,
_acceptRect,
_acceptRectOver,
ripple,
_acceptText,
_acceptTextWidth,
outerWidth,
over);
}
void Controller::rowPaintReject(
Painter &p,
QRect geometry,
std::unique_ptr<Ui::RippleAnimation> &ripple,
int outerWidth,
bool over) {
paintButton(
p,
geometry,
st::requestsRejectButton,
_rejectRect,
_rejectRectOver,
ripple,
_rejectText,
_rejectTextWidth,
outerWidth,
over);
}
void Controller::paintButton(
Painter &p,
QRect geometry,
const style::RoundButton &st,
const Ui::RoundRect &rect,
const Ui::RoundRect &rectOver,
std::unique_ptr<Ui::RippleAnimation> &ripple,
const QString &text,
int textWidth,
int outerWidth,
bool over) {
rect.paint(p, geometry);
if (over) {
rectOver.paint(p, geometry);
}
if (ripple) {
ripple->paint(p, geometry.x(), geometry.y(), outerWidth);
if (ripple->empty()) {
ripple = nullptr;
}
}
p.setFont(st.style.font);
p.setPen(over ? st.textFgOver : st.textFg);
p.drawTextLeft(
geometry.x() + (geometry.width() - textWidth) / 2,
geometry.y() + st.textTop,
outerWidth,
text);
}
} // namespace
void ShowCommunityPendingRequestsBox(
not_null<Window::SessionNavigation*> navigation,
not_null<ChannelData*> community) {
auto controller = std::make_unique<Controller>(navigation, community);
const auto init = [=](not_null<PeerListBox*> box) {
const auto count = std::max(community->pendingRequestsCount(), 1);
const auto processAll = [=](bool reject) {
const auto sure = [=](Fn<void()> &&close) {
close();
community->session().api().communities(
).toggleAllPeerLinkRequestApproval(
community,
reject,
crl::guard(box, [=] { box->closeBox(); }),
crl::guard(box, [=](const QString &error) {
box->uiShow()->showToast(error);
}));
};
box->uiShow()->showBox(Ui::MakeConfirmBox({
.text = (reject
? tr::lng_community_requests_decline_all_sure(
tr::now,
lt_count,
count)
: tr::lng_community_requests_add_all_sure(
tr::now,
lt_count,
count)),
.confirmed = sure,
.confirmText = (reject
? tr::lng_community_request_decline()
: tr::lng_community_request_add()),
.title = (reject
? tr::lng_community_requests_decline_all_title()
: tr::lng_community_requests_add_all_title()),
}));
};
box->addButton(
tr::lng_community_requests_add_all(),
[=] { processAll(false); });
box->addLeftButton(
tr::lng_community_requests_decline_all(),
[=] { processAll(true); });
auto above = object_ptr<Ui::VerticalLayout>(box);
Ui::AddDividerText(above, tr::lng_community_requests_about());
Ui::AddSkip(above);
Ui::AddSubsectionTitle(
above,
tr::lng_community_requests_count(
lt_count,
rpl::single(float64(count))));
box->peerListSetAboveWidget(std::move(above));
};
navigation->uiShow()->showBox(Box<PeerListBox>(
std::move(controller),
init));
}
@@ -0,0 +1,18 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
class ChannelData;
namespace Window {
class SessionNavigation;
} // namespace Window
void ShowCommunityPendingRequestsBox(
not_null<Window::SessionNavigation*> navigation,
not_null<ChannelData*> community);
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/manage_community_box.h"
#include "apiwrap.h"
#include "boxes/peers/community_pending_requests_box.h"
#include "boxes/peers/edit_participants_box.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
@@ -23,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/checkbox.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
@@ -179,6 +181,28 @@ void ManageCommunityBox(
community,
ParticipantsBoxController::Role::Admins);
});
if (community->canManageLinkedPeers()) {
const auto wrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
auto count = Info::Profile::PendingRequestsCountValue(
community
) | rpl::start_spawning(wrap->lifetime());
Settings::AddButtonWithLabel(
wrap->entity(),
tr::lng_community_requests_title(),
PositiveNumberString(rpl::duplicate(count)),
st::settingsButton,
{ &st::menuIconInvite }
)->addClickHandler([=] {
ShowCommunityPendingRequestsBox(navigation, community);
});
wrap->toggleOn(
std::move(count) | rpl::map(rpl::mappers::_1 > 0),
anim::type::instant);
wrap->finishAnimating();
}
Settings::AddButtonWithLabel(
container,
tr::lng_manage_peer_removed_users(),