mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Support complex invite-users responses.
This commit is contained in:
parent
3cdb528dfe
commit
c0117e72ad
15 changed files with 631 additions and 43 deletions
|
@ -1319,6 +1319,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_profile_photo_by_you" = "photo set by you";
|
||||
"lng_profile_public_photo" = "public photo";
|
||||
|
||||
"lng_invite_upgrade_title" = "Upgrade to Premium";
|
||||
"lng_invite_upgrade_group_invite#one" = "{users} only accepts invitations to groups from Contacts and **Premium** users.";
|
||||
"lng_invite_upgrade_group_invite#other" = "{users} only accept invitations to groups from Contacts and **Premium** users.";
|
||||
"lng_invite_upgrade_group_write#one" = "{users} only accepts messages and invitations to groups from Contacts and **Premium** users.";
|
||||
"lng_invite_upgrade_group_write#other" = "{users} only accept messages and invitations to groups from Contacts and **Premium** users.";
|
||||
"lng_invite_upgrade_channel_invite#one" = "{users} only accepts invitations to channels from Contacts and **Premium** users.";
|
||||
"lng_invite_upgrade_channel_invite#other" = "{users} only accept invitations to channels from Contacts and **Premium** users.";
|
||||
"lng_invite_upgrade_channel_write#one" = "{users} only accepts messages and invitations to channels from Contacts and **Premium** users.";
|
||||
"lng_invite_upgrade_channel_write#other" = "{users} only accept messages and invitations to channels from Contacts and **Premium** users.";
|
||||
"lng_invite_upgrade_users_few" = "{users} and {last}";
|
||||
"lng_invite_upgrade_users_many#one" = "{users} and **{count}** more person";
|
||||
"lng_invite_upgrade_users_many#other" = "{users} and **{count}** more people";
|
||||
"lng_invite_upgrade_or" = "or";
|
||||
"lng_invite_upgrade_via_title" = "Invite via Link";
|
||||
"lng_invite_upgrade_via_group_about" = "You can send an invite link to the group in a private message instead.";
|
||||
"lng_invite_upgrade_via_channel_about" = "You can send an invite link to the channel in a private message instead.";
|
||||
"lng_invite_status_disabled" = "available only to Premium users";
|
||||
|
||||
"lng_via_link_group_one" = "**{user}** restricts adding them to groups.\nYou can send them an invite link as message instead.";
|
||||
"lng_via_link_group_many#one" = "**{count} user** restricts adding them to groups.\nYou can send them an invite link as message instead.";
|
||||
"lng_via_link_group_many#other" = "**{count} users** restrict adding them to groups.\nYou can send them an invite link as message instead.";
|
||||
|
|
|
@ -502,16 +502,24 @@ void ChatParticipants::add(
|
|||
const auto &data = result.data();
|
||||
chat->session().api().applyUpdates(data.vupdates());
|
||||
if (done) done(true);
|
||||
ChatInviteForbidden(
|
||||
show,
|
||||
chat,
|
||||
CollectForbiddenUsers(&chat->session(), result));
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
const auto type = error.type();
|
||||
ShowAddParticipantsError(show, type, peer, { 1, user });
|
||||
ShowAddParticipantsError(show, type, peer, user);
|
||||
if (done) done(false);
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
}
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
const auto hasBot = ranges::any_of(users, &UserData::isBot);
|
||||
if (!peer->isMegagroup() && hasBot) {
|
||||
ShowAddParticipantsError(show, "USER_BOT", peer, users);
|
||||
ShowAddParticipantsError(
|
||||
show,
|
||||
u"USER_BOT"_q,
|
||||
peer,
|
||||
{ .users = users });
|
||||
return;
|
||||
}
|
||||
auto list = QVector<MTPInputUser>();
|
||||
|
@ -531,7 +539,9 @@ void ChatParticipants::add(
|
|||
channel,
|
||||
CollectForbiddenUsers(&channel->session(), result));
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
ShowAddParticipantsError(show, error.type(), peer, users);
|
||||
ShowAddParticipantsError(show, error.type(), peer, {
|
||||
.users = users,
|
||||
});
|
||||
if (callback) callback(false);
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
};
|
||||
|
|
|
@ -3862,7 +3862,7 @@ void ApiWrap::sendBotStart(
|
|||
Expects(bot->isBot());
|
||||
|
||||
if (chat && chat->isChannel() && !chat->isMegagroup()) {
|
||||
ShowAddParticipantsError(show, "USER_BOT", chat, { 1, bot });
|
||||
ShowAddParticipantsError(show, "USER_BOT", chat, bot);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -3894,7 +3894,7 @@ void ApiWrap::sendBotStart(
|
|||
}).fail([=](const MTP::Error &error) {
|
||||
if (chat) {
|
||||
const auto type = error.type();
|
||||
ShowAddParticipantsError(show, type, chat, { 1, bot });
|
||||
ShowAddParticipantsError(show, type, chat, bot);
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
|
|
@ -172,16 +172,28 @@ void ShowAddParticipantsError(
|
|||
std::shared_ptr<Ui::Show> show,
|
||||
const QString &error,
|
||||
not_null<PeerData*> chat,
|
||||
const std::vector<not_null<UserData*>> &users) {
|
||||
not_null<UserData*> user) {
|
||||
ShowAddParticipantsError(
|
||||
std::move(show),
|
||||
error,
|
||||
chat,
|
||||
{ .users = { 1, user } });
|
||||
}
|
||||
|
||||
void ShowAddParticipantsError(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
const QString &error,
|
||||
not_null<PeerData*> chat,
|
||||
const ForbiddenInvites &forbidden) {
|
||||
if (error == u"USER_BOT"_q) {
|
||||
const auto channel = chat->asChannel();
|
||||
if ((users.size() == 1)
|
||||
&& users.front()->isBot()
|
||||
if ((forbidden.users.size() == 1)
|
||||
&& forbidden.users.front()->isBot()
|
||||
&& channel
|
||||
&& !channel->isMegagroup()
|
||||
&& channel->canAddAdmins()) {
|
||||
const auto makeAdmin = [=] {
|
||||
const auto user = users.front();
|
||||
const auto user = forbidden.users.front();
|
||||
const auto weak = std::make_shared<QPointer<EditAdminBox>>();
|
||||
const auto close = [=](auto&&...) {
|
||||
if (*weak) {
|
||||
|
@ -213,7 +225,7 @@ void ShowAddParticipantsError(
|
|||
return;
|
||||
}
|
||||
}
|
||||
const auto hasBot = ranges::any_of(users, &UserData::isBot);
|
||||
const auto hasBot = ranges::any_of(forbidden.users, &UserData::isBot);
|
||||
if (error == u"PEER_FLOOD"_q) {
|
||||
const auto type = (chat->isChat() || chat->isMegagroup())
|
||||
? PeerFloodType::InviteGroup
|
||||
|
@ -222,7 +234,7 @@ void ShowAddParticipantsError(
|
|||
Ui::show(Ui::MakeInformBox(text), Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
} else if (error == u"USER_PRIVACY_RESTRICTED"_q) {
|
||||
ChatInviteForbidden(show, chat, users);
|
||||
ChatInviteForbidden(show, chat, forbidden);
|
||||
return;
|
||||
}
|
||||
const auto text = [&] {
|
||||
|
|
|
@ -43,6 +43,8 @@ enum class PeerFloodType {
|
|||
InviteChannel,
|
||||
};
|
||||
|
||||
struct ForbiddenInvites;
|
||||
|
||||
[[nodiscard]] TextWithEntities PeerFloodErrorText(
|
||||
not_null<Main::Session*> session,
|
||||
PeerFloodType type);
|
||||
|
@ -50,7 +52,12 @@ void ShowAddParticipantsError(
|
|||
std::shared_ptr<Ui::Show> show,
|
||||
const QString &error,
|
||||
not_null<PeerData*> chat,
|
||||
const std::vector<not_null<UserData*>> &users);
|
||||
const ForbiddenInvites &forbidden);
|
||||
void ShowAddParticipantsError(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
const QString &error,
|
||||
not_null<PeerData*> chat,
|
||||
not_null<UserData*> user);
|
||||
|
||||
class AddContactBox : public Ui::BoxContent {
|
||||
public:
|
||||
|
|
|
@ -1028,3 +1028,19 @@ birthdayLabel: FlatLabel(infoLabel) {
|
|||
margin: margins(0px, 0px, 0px, 0px);
|
||||
}
|
||||
birthdayTodayIcon: icon {{ "menu/gift_premium", windowActiveTextFg }};
|
||||
|
||||
inviteForbiddenUserpicsPadding: margins(10px, 10px, 10px, 0px);
|
||||
inviteForbiddenInfo: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 240px;
|
||||
align: align(top);
|
||||
}
|
||||
inviteForbiddenInfoPadding: margins(32px, 10px, 32px, 4px);
|
||||
inviteForbiddenSubscribePadding: margins(16px, 12px, 16px, 16px);
|
||||
inviteForbiddenOrLabelPadding: margins(32px, 0px, 32px, 0px);
|
||||
inviteForbiddenTitle: FlatLabel(boxTitle) {
|
||||
minWidth: 120px;
|
||||
align: align(top);
|
||||
}
|
||||
inviteForbiddenTitlePadding: margins(32px, 4px, 32px, 0px);
|
||||
inviteForbiddenLockBg: dialogsUnreadBgMuted;
|
||||
inviteForbiddenLockIcon: icon {{ "emoji/premium_lock", dialogsUnreadFg }};
|
||||
|
|
|
@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "api/api_invite_links.h"
|
||||
#include "boxes/peers/edit_participant_box.h"
|
||||
#include "boxes/peers/edit_peer_type_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/peers/replace_boost_box.h"
|
||||
#include "boxes/max_invite_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_channel.h"
|
||||
|
@ -20,32 +20,56 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_session.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "history/history.h"
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/boxes/show_or_premium_box.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/text/text_utilities.h" // Ui::Text::RichLangValue
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/gradient_round_button.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/painter.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "info/profile/info_profile_icon.h"
|
||||
#include "apiwrap.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_premium.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kParticipantsFirstPageCount = 16;
|
||||
constexpr auto kParticipantsPerPage = 200;
|
||||
constexpr auto kUserpicsLimit = 3;
|
||||
|
||||
class ForbiddenRow final : public PeerListRow {
|
||||
public:
|
||||
ForbiddenRow(not_null<PeerData*> peer, bool locked);
|
||||
|
||||
PaintRoundImageCallback generatePaintUserpicCallback(
|
||||
bool forceRound) override;
|
||||
|
||||
private:
|
||||
const bool _locked = false;
|
||||
QImage _disabledFrame;
|
||||
InMemoryKey _userpicKey;
|
||||
int _paletteVersion = 0;
|
||||
|
||||
};
|
||||
|
||||
class InviteForbiddenController final : public PeerListController {
|
||||
public:
|
||||
InviteForbiddenController(
|
||||
not_null<PeerData*> peer,
|
||||
std::vector<not_null<UserData*>> users);
|
||||
ForbiddenInvites forbidden);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
|
@ -67,9 +91,14 @@ private:
|
|||
void appendRow(not_null<UserData*> user);
|
||||
[[nodiscard]] std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool canInvite(not_null<PeerData*> peer) const;
|
||||
|
||||
void setSimpleCover();
|
||||
void setComplexCover();
|
||||
|
||||
const not_null<PeerData*> _peer;
|
||||
const std::vector<not_null<UserData*>> _users;
|
||||
const ForbiddenInvites _forbidden;
|
||||
const std::vector<not_null<UserData*>> &_users;
|
||||
const bool _can = false;
|
||||
rpl::variable<int> _selected;
|
||||
bool _sending = false;
|
||||
|
@ -91,22 +120,226 @@ base::flat_set<not_null<UserData*>> GetAlreadyInFromPeer(PeerData *peer) {
|
|||
return {};
|
||||
}
|
||||
|
||||
void FillUpgradeToPremiumCover(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<PeerData*> peer,
|
||||
const ForbiddenInvites &forbidden) {
|
||||
const auto noneCanSend = (forbidden.premiumAllowsWrite.size()
|
||||
== forbidden.users.size());
|
||||
const auto &userpicUsers = (forbidden.premiumAllowsInvite.empty()
|
||||
|| noneCanSend)
|
||||
? forbidden.premiumAllowsWrite
|
||||
: forbidden.premiumAllowsInvite;
|
||||
Assert(!userpicUsers.empty());
|
||||
|
||||
auto userpicPeers = userpicUsers | ranges::views::transform([](auto u) {
|
||||
return not_null<PeerData*>(u);
|
||||
}) | ranges::to_vector;
|
||||
container->add(object_ptr<Ui::PaddingWrap<>>(
|
||||
container,
|
||||
CreateUserpicsWithMoreBadge(
|
||||
container,
|
||||
rpl::single(std::move(userpicPeers)),
|
||||
kUserpicsLimit),
|
||||
st::inviteForbiddenUserpicsPadding)
|
||||
)->entity()->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
const auto users = int(userpicUsers.size());
|
||||
const auto names = std::min(users, kUserpicsLimit);
|
||||
const auto remaining = std::max(users - kUserpicsLimit, 0);
|
||||
auto text = TextWithEntities();
|
||||
for (auto i = 0; i != names; ++i) {
|
||||
const auto name = userpicUsers[i]->shortName();
|
||||
if (text.empty()) {
|
||||
text = Ui::Text::Bold(name);
|
||||
} else if (i == names - 1 && !remaining) {
|
||||
text = tr::lng_invite_upgrade_users_few(
|
||||
tr::now,
|
||||
lt_users,
|
||||
text,
|
||||
lt_last,
|
||||
Ui::Text::Bold(name),
|
||||
Ui::Text::RichLangValue);
|
||||
} else {
|
||||
text.append(", ").append(Ui::Text::Bold(name));
|
||||
}
|
||||
}
|
||||
if (remaining > 0) {
|
||||
text = tr::lng_invite_upgrade_users_many(
|
||||
tr::now,
|
||||
lt_count,
|
||||
remaining,
|
||||
lt_users,
|
||||
text,
|
||||
Ui::Text::RichLangValue);
|
||||
}
|
||||
const auto inviteOnly = !forbidden.premiumAllowsInvite.empty()
|
||||
&& (forbidden.premiumAllowsWrite.size() != forbidden.users.size());
|
||||
text = (peer->isBroadcast()
|
||||
? (inviteOnly
|
||||
? tr::lng_invite_upgrade_channel_invite
|
||||
: tr::lng_invite_upgrade_channel_write)
|
||||
: (inviteOnly
|
||||
? tr::lng_invite_upgrade_group_invite
|
||||
: tr::lng_invite_upgrade_group_write))(
|
||||
tr::now,
|
||||
lt_count,
|
||||
int(userpicUsers.size()),
|
||||
lt_users,
|
||||
text,
|
||||
Ui::Text::RichLangValue);
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
rpl::single(text),
|
||||
st::inviteForbiddenInfo),
|
||||
st::inviteForbiddenInfoPadding);
|
||||
}
|
||||
|
||||
void SimpleForbiddenBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<PeerData*> peer,
|
||||
const ForbiddenInvites &forbidden) {
|
||||
box->setTitle(tr::lng_invite_upgrade_title());
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->addTopButton(st::boxTitleClose, [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
|
||||
auto sshow = Main::MakeSessionShow(box->uiShow(), &peer->session());
|
||||
const auto container = box->verticalLayout();
|
||||
FillUpgradeToPremiumCover(container, sshow, peer, forbidden);
|
||||
|
||||
const auto &stButton = st::premiumGiftBox;
|
||||
box->setStyle(stButton);
|
||||
auto raw = Settings::CreateSubscribeButton(
|
||||
sshow,
|
||||
ChatHelpers::ResolveWindowDefault(),
|
||||
{
|
||||
.parent = container,
|
||||
.computeRef = [] { return u"invite_privacy"_q; },
|
||||
.text = tr::lng_messages_privacy_premium_button(),
|
||||
.showPromo = true,
|
||||
});
|
||||
auto button = object_ptr<Ui::GradientButton>::fromRaw(raw);
|
||||
button->resizeToWidth(st::boxWideWidth
|
||||
- stButton.buttonPadding.left()
|
||||
- stButton.buttonPadding.right());
|
||||
box->setShowFinishedCallback([raw = button.data()] {
|
||||
raw->startGlareAnimation();
|
||||
});
|
||||
box->addButton(std::move(button));
|
||||
|
||||
Data::AmPremiumValue(
|
||||
&peer->session()
|
||||
) | rpl::skip(1) | rpl::start_with_next([=] {
|
||||
box->closeBox();
|
||||
}, box->lifetime());
|
||||
}
|
||||
|
||||
InviteForbiddenController::InviteForbiddenController(
|
||||
not_null<PeerData*> peer,
|
||||
std::vector<not_null<UserData*>> users)
|
||||
ForbiddenInvites forbidden)
|
||||
: _peer(peer)
|
||||
, _users(std::move(users))
|
||||
, _forbidden(std::move(forbidden))
|
||||
, _users(_forbidden.users)
|
||||
, _can(peer->isChat()
|
||||
? peer->asChat()->canHaveInviteLink()
|
||||
: peer->asChannel()->canHaveInviteLink())
|
||||
, _selected(_can ? int(_users.size()) : 0) {
|
||||
, _selected(_can
|
||||
? (int(_users.size()) - int(_forbidden.premiumAllowsWrite.size()))
|
||||
: 0) {
|
||||
}
|
||||
|
||||
Main::Session &InviteForbiddenController::session() const {
|
||||
return _peer->session();
|
||||
}
|
||||
|
||||
void InviteForbiddenController::prepare() {
|
||||
ForbiddenRow::ForbiddenRow(not_null<PeerData*> peer, bool locked)
|
||||
: PeerListRow(peer)
|
||||
, _locked(locked) {
|
||||
if (_locked) {
|
||||
setCustomStatus(tr::lng_invite_status_disabled(tr::now));
|
||||
}
|
||||
}
|
||||
|
||||
PaintRoundImageCallback ForbiddenRow::generatePaintUserpicCallback(
|
||||
bool forceRound) {
|
||||
const auto peer = this->peer();
|
||||
const auto saved = peer->isSelf();
|
||||
const auto replies = peer->isRepliesChat();
|
||||
auto userpic = (saved || replies)
|
||||
? Ui::PeerUserpicView()
|
||||
: ensureUserpicView();
|
||||
auto paint = [=](
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
int size) mutable {
|
||||
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||
};
|
||||
if (!_locked) {
|
||||
return paint;
|
||||
}
|
||||
return [=](
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
int size) mutable {
|
||||
const auto wide = size + style::ConvertScale(3);
|
||||
const auto full = QSize(wide, wide) * style::DevicePixelRatio();
|
||||
auto repaint = false;
|
||||
if (_disabledFrame.size() != full) {
|
||||
repaint = true;
|
||||
_disabledFrame = QImage(
|
||||
full,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_disabledFrame.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
} else {
|
||||
repaint = (_paletteVersion != style::PaletteVersion())
|
||||
|| (!saved
|
||||
&& !replies
|
||||
&& (_userpicKey != peer->userpicUniqueKey(userpic)));
|
||||
}
|
||||
if (repaint) {
|
||||
_paletteVersion = style::PaletteVersion();
|
||||
_userpicKey = peer->userpicUniqueKey(userpic);
|
||||
|
||||
_disabledFrame.fill(Qt::transparent);
|
||||
auto p = Painter(&_disabledFrame);
|
||||
paint(p, 0, 0, wide, size);
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setBrush(st::boxBg);
|
||||
p.setPen(Qt::NoPen);
|
||||
const auto lock = st::inviteForbiddenLockIcon.size();
|
||||
const auto stroke = style::ConvertScale(2);
|
||||
const auto inner = QRect(
|
||||
size + (stroke / 2) - lock.width(),
|
||||
size + (stroke / 2) - lock.height(),
|
||||
lock.width(),
|
||||
lock.height());
|
||||
const auto half = stroke / 2.;
|
||||
const auto rect = QRectF(inner).marginsAdded(
|
||||
{ half, half, half, half });
|
||||
auto pen = st::boxBg->p;
|
||||
pen.setWidthF(stroke);
|
||||
p.setPen(pen);
|
||||
p.setBrush(st::inviteForbiddenLockBg);
|
||||
p.drawEllipse(rect);
|
||||
|
||||
st::inviteForbiddenLockIcon.paintInCenter(p, inner);
|
||||
}
|
||||
p.drawImage(x, y, _disabledFrame);
|
||||
};
|
||||
}
|
||||
|
||||
void InviteForbiddenController::setSimpleCover() {
|
||||
delegate()->peerListSetTitle(
|
||||
_can ? tr::lng_profile_add_via_link() : tr::lng_via_link_cant());
|
||||
const auto broadcast = _peer->isBroadcast();
|
||||
const auto count = int(_users.size());
|
||||
const auto phraseCounted = !_can
|
||||
|
@ -135,8 +368,78 @@ void InviteForbiddenController::prepare() {
|
|||
std::move(text),
|
||||
st::requestPeerRestriction),
|
||||
st::boxRowPadding));
|
||||
delegate()->peerListSetTitle(
|
||||
_can ? tr::lng_profile_add_via_link() : tr::lng_via_link_cant());
|
||||
}
|
||||
|
||||
void InviteForbiddenController::setComplexCover() {
|
||||
delegate()->peerListSetTitle(tr::lng_invite_upgrade_title());
|
||||
|
||||
auto cover = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
|
||||
const auto container = cover.data();
|
||||
const auto show = delegate()->peerListUiShow();
|
||||
FillUpgradeToPremiumCover(container, show, _peer, _forbidden);
|
||||
|
||||
container->add(
|
||||
object_ptr<Ui::GradientButton>::fromRaw(
|
||||
Settings::CreateSubscribeButton(
|
||||
show,
|
||||
ChatHelpers::ResolveWindowDefault(),
|
||||
{
|
||||
.parent = container,
|
||||
.computeRef = [] { return u"invite_privacy"_q; },
|
||||
.text = tr::lng_messages_privacy_premium_button(),
|
||||
})),
|
||||
st::inviteForbiddenSubscribePadding);
|
||||
|
||||
if (_forbidden.users.size() > _forbidden.premiumAllowsWrite.size()) {
|
||||
if (_can) {
|
||||
container->add(
|
||||
MakeShowOrLabel(container, tr::lng_invite_upgrade_or()),
|
||||
st::inviteForbiddenOrLabelPadding);
|
||||
}
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
(_can
|
||||
? tr::lng_invite_upgrade_via_title()
|
||||
: tr::lng_via_link_cant()),
|
||||
st::inviteForbiddenTitle),
|
||||
st::inviteForbiddenTitlePadding);
|
||||
|
||||
const auto about = _can
|
||||
? (_peer->isBroadcast()
|
||||
? tr::lng_invite_upgrade_via_channel_about
|
||||
: tr::lng_invite_upgrade_via_group_about)(
|
||||
tr::now,
|
||||
Ui::Text::WithEntities)
|
||||
: (_forbidden.users.size() == 1
|
||||
? tr::lng_via_link_cant_one(
|
||||
tr::now,
|
||||
lt_user,
|
||||
TextWithEntities{ _forbidden.users.front()->shortName() },
|
||||
Ui::Text::RichLangValue)
|
||||
: tr::lng_via_link_cant_many(
|
||||
tr::now,
|
||||
lt_count,
|
||||
int(_forbidden.users.size()),
|
||||
Ui::Text::RichLangValue));
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
rpl::single(about),
|
||||
st::inviteForbiddenInfo),
|
||||
st::inviteForbiddenInfoPadding);
|
||||
}
|
||||
delegate()->peerListSetAboveWidget(std::move(cover));
|
||||
}
|
||||
|
||||
void InviteForbiddenController::prepare() {
|
||||
if (session().premium()
|
||||
|| (_forbidden.premiumAllowsInvite.empty()
|
||||
&& _forbidden.premiumAllowsWrite.empty())) {
|
||||
setSimpleCover();
|
||||
} else {
|
||||
setComplexCover();
|
||||
}
|
||||
|
||||
for (const auto &user : _users) {
|
||||
appendRow(user);
|
||||
|
@ -144,8 +447,16 @@ void InviteForbiddenController::prepare() {
|
|||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
bool InviteForbiddenController::canInvite(not_null<PeerData*> peer) const {
|
||||
const auto user = peer->asUser();
|
||||
Assert(user != nullptr);
|
||||
|
||||
return _can
|
||||
&& !ranges::contains(_forbidden.premiumAllowsWrite, not_null(user));
|
||||
}
|
||||
|
||||
void InviteForbiddenController::rowClicked(not_null<PeerListRow*> row) {
|
||||
if (!_can) {
|
||||
if (!canInvite(row->peer())) {
|
||||
return;
|
||||
}
|
||||
const auto checked = row->checked();
|
||||
|
@ -158,7 +469,7 @@ void InviteForbiddenController::appendRow(not_null<UserData*> user) {
|
|||
auto row = createRow(user);
|
||||
const auto raw = row.get();
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
if (_can) {
|
||||
if (canInvite(user)) {
|
||||
delegate()->peerListSetRowChecked(raw, true);
|
||||
}
|
||||
}
|
||||
|
@ -228,7 +539,8 @@ void InviteForbiddenController::send(
|
|||
|
||||
std::unique_ptr<PeerListRow> InviteForbiddenController::createRow(
|
||||
not_null<UserData*> user) const {
|
||||
return std::make_unique<PeerListRow>(user);
|
||||
const auto locked = _can && !canInvite(user);
|
||||
return std::make_unique<ForbiddenRow>(user, locked);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -559,17 +871,23 @@ void AddParticipantsBoxController::Start(
|
|||
Start(navigation, channel, {}, true);
|
||||
}
|
||||
|
||||
std::vector<not_null<UserData*>> CollectForbiddenUsers(
|
||||
ForbiddenInvites CollectForbiddenUsers(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPmessages_InvitedUsers &result) {
|
||||
const auto &data = result.data();
|
||||
const auto owner = &session->data();
|
||||
auto forbidden = std::vector<not_null<UserData*>>();
|
||||
auto forbidden = ForbiddenInvites();
|
||||
for (const auto &missing : data.vmissing_invitees().v) {
|
||||
const auto user = owner->userLoaded(missing.data().vuser_id());
|
||||
const auto &data = missing.data();
|
||||
const auto user = owner->userLoaded(data.vuser_id());
|
||||
if (user) {
|
||||
// #TODO invites
|
||||
forbidden.push_back(user);
|
||||
forbidden.users.push_back(user);
|
||||
if (data.is_premium_would_allow_invite()) {
|
||||
forbidden.premiumAllowsInvite.push_back(user);
|
||||
}
|
||||
if (data.is_premium_required_for_pm()) {
|
||||
forbidden.premiumAllowsWrite.push_back(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
return forbidden;
|
||||
|
@ -578,9 +896,14 @@ std::vector<not_null<UserData*>> CollectForbiddenUsers(
|
|||
bool ChatInviteForbidden(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
std::vector<not_null<UserData*>> forbidden) {
|
||||
ForbiddenInvites forbidden) {
|
||||
if (forbidden.empty() || !show || !show->valid()) {
|
||||
return false;
|
||||
} else if (forbidden.users.size() <= kUserpicsLimit
|
||||
&& (forbidden.premiumAllowsWrite.size()
|
||||
== forbidden.users.size())) {
|
||||
show->show(Box(SimpleForbiddenBox, peer, forbidden));
|
||||
return true;
|
||||
}
|
||||
auto controller = std::make_unique<InviteForbiddenController>(
|
||||
peer,
|
||||
|
@ -612,6 +935,12 @@ bool ChatInviteForbidden(
|
|||
box->closeBox();
|
||||
});
|
||||
}, box->lifetime());
|
||||
|
||||
Data::AmPremiumValue(
|
||||
&peer->session()
|
||||
) | rpl::skip(1) | rpl::start_with_next([=] {
|
||||
box->closeBox();
|
||||
}, box->lifetime());
|
||||
};
|
||||
show->showBox(
|
||||
Box<PeerListBox>(std::move(controller), std::move(initBox)));
|
||||
|
|
|
@ -73,13 +73,22 @@ private:
|
|||
|
||||
};
|
||||
|
||||
[[nodiscard]] std::vector<not_null<UserData*>> CollectForbiddenUsers(
|
||||
struct ForbiddenInvites {
|
||||
std::vector<not_null<UserData*>> users;
|
||||
std::vector<not_null<UserData*>> premiumAllowsInvite;
|
||||
std::vector<not_null<UserData*>> premiumAllowsWrite;
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return users.empty();
|
||||
}
|
||||
};
|
||||
[[nodiscard]] ForbiddenInvites CollectForbiddenUsers(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPmessages_InvitedUsers &result);
|
||||
bool ChatInviteForbidden(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
std::vector<not_null<UserData*>> forbidden);
|
||||
ForbiddenInvites forbidden);
|
||||
|
||||
// Adding an admin, banned or restricted user from channel members
|
||||
// with search + contacts search + global search.
|
||||
|
|
|
@ -87,8 +87,12 @@ void AddChatParticipant(
|
|||
if (onDone) {
|
||||
onDone();
|
||||
}
|
||||
ChatInviteForbidden(
|
||||
show,
|
||||
chat,
|
||||
CollectForbiddenUsers(&chat->session(), result));
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
ShowAddParticipantsError(show, error.type(), chat, { 1, user });
|
||||
ShowAddParticipantsError(show, error.type(), chat, user);
|
||||
if (onFail) {
|
||||
onFail();
|
||||
}
|
||||
|
@ -155,7 +159,7 @@ void SaveChannelAdmin(
|
|||
onDone();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
ShowAddParticipantsError(show, error.type(), channel, { 1, user });
|
||||
ShowAddParticipantsError(show, error.type(), channel, user);
|
||||
if (onFail) {
|
||||
onFail();
|
||||
}
|
||||
|
|
|
@ -671,3 +671,143 @@ object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
|
|||
}, overlay->lifetime());
|
||||
return result;
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> CreateUserpicsWithMoreBadge(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<std::vector<not_null<PeerData*>>> peers,
|
||||
int limit) {
|
||||
struct State {
|
||||
std::vector<not_null<PeerData*>> from;
|
||||
std::vector<std::unique_ptr<Ui::UserpicButton>> buttons;
|
||||
QImage layer;
|
||||
QImage badge;
|
||||
rpl::variable<int> count = 0;
|
||||
bool painting = false;
|
||||
};
|
||||
const auto full = st::boostReplaceUserpic.size.height()
|
||||
+ st::boostReplaceIconAdd.y()
|
||||
+ st::lineWidth;
|
||||
auto result = object_ptr<Ui::FixedHeightWidget>(parent, full);
|
||||
const auto raw = result.data();
|
||||
const auto &st = st::boostReplaceUserpic;
|
||||
const auto overlay = CreateChild<Ui::RpWidget>(raw);
|
||||
|
||||
const auto state = raw->lifetime().make_state<State>();
|
||||
std::move(
|
||||
peers
|
||||
) | rpl::start_with_next([=](
|
||||
const std::vector<not_null<PeerData*>> &list) {
|
||||
const auto &st = st::boostReplaceUserpic;
|
||||
auto was = base::take(state->from);
|
||||
auto buttons = base::take(state->buttons);
|
||||
state->from.reserve(list.size());
|
||||
state->buttons.reserve(list.size());
|
||||
for (const auto &peer : list | ranges::views::take(limit)) {
|
||||
state->from.push_back(peer);
|
||||
const auto i = ranges::find(was, peer);
|
||||
if (i != end(was)) {
|
||||
const auto index = int(i - begin(was));
|
||||
Assert(buttons[index] != nullptr);
|
||||
state->buttons.push_back(std::move(buttons[index]));
|
||||
} else {
|
||||
state->buttons.push_back(
|
||||
std::make_unique<Ui::UserpicButton>(raw, peer, st));
|
||||
const auto raw = state->buttons.back().get();
|
||||
base::install_event_filter(raw, [=](not_null<QEvent*> e) {
|
||||
return (e->type() == QEvent::Paint && !state->painting)
|
||||
? base::EventFilterResult::Cancel
|
||||
: base::EventFilterResult::Continue;
|
||||
});
|
||||
}
|
||||
}
|
||||
state->count.force_assign(int(list.size()));
|
||||
overlay->update();
|
||||
}, raw->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
raw->widthValue(),
|
||||
state->count.value()
|
||||
) | rpl::start_with_next([=](int width, int count) {
|
||||
const auto &st = st::boostReplaceUserpic;
|
||||
const auto single = st.size.width();
|
||||
const auto left = width - single;
|
||||
const auto used = std::min(count, int(state->buttons.size()));
|
||||
const auto shift = std::min(
|
||||
st::boostReplaceUserpicsShift,
|
||||
(used > 1 ? (left / (used - 1)) : width));
|
||||
const auto total = used ? (single + (used - 1) * shift) : 0;
|
||||
auto x = (width - total) / 2;
|
||||
for (const auto &single : state->buttons) {
|
||||
single->moveToLeft(x, 0);
|
||||
x += shift;
|
||||
}
|
||||
overlay->setGeometry(QRect(0, 0, width, raw->height()));
|
||||
}, raw->lifetime());
|
||||
|
||||
overlay->paintRequest(
|
||||
) | rpl::filter([=] {
|
||||
return !state->buttons.empty();
|
||||
}) | rpl::start_with_next([=] {
|
||||
const auto outerw = overlay->width();
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
if (state->layer.size() != QSize(outerw, full) * ratio) {
|
||||
state->layer = QImage(
|
||||
QSize(outerw, full) * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
state->layer.setDevicePixelRatio(ratio);
|
||||
}
|
||||
state->layer.fill(Qt::transparent);
|
||||
|
||||
auto q = QPainter(&state->layer);
|
||||
auto hq = PainterHighQualityEnabler(q);
|
||||
const auto stroke = st::boostReplaceIconOutline;
|
||||
const auto half = stroke / 2.;
|
||||
auto pen = st::windowBg->p;
|
||||
pen.setWidthF(stroke * 2.);
|
||||
state->painting = true;
|
||||
for (const auto &button : state->buttons) {
|
||||
q.setPen(pen);
|
||||
q.setBrush(Qt::NoBrush);
|
||||
q.drawEllipse(button->geometry());
|
||||
const auto position = button->pos();
|
||||
button->render(&q, position, QRegion(), QWidget::DrawChildren);
|
||||
}
|
||||
state->painting = false;
|
||||
const auto last = state->buttons.back().get();
|
||||
const auto add = st::boostReplaceIconAdd;
|
||||
const auto skip = st::boostReplaceIconSkip;
|
||||
const auto w = st::boostReplaceIcon.width() + 2 * skip;
|
||||
const auto h = st::boostReplaceIcon.height() + 2 * skip;
|
||||
const auto x = last->x() + last->width() - w + add.x();
|
||||
const auto y = last->y() + last->height() - h + add.y();
|
||||
|
||||
const auto text = (state->count.current() > limit)
|
||||
? ('+' + QString::number(state->count.current() - limit))
|
||||
: QString();
|
||||
if (!text.isEmpty()) {
|
||||
const auto &font = st::semiboldFont;
|
||||
const auto width = font->width(text);
|
||||
const auto padded = std::max(w, width + 2 * font->spacew);
|
||||
const auto rect = QRect(x - (padded - w) / 2, y, padded, h);
|
||||
auto brush = QLinearGradient(rect.bottomRight(), rect.topLeft());
|
||||
brush.setStops(Ui::Premium::ButtonGradientStops());
|
||||
q.setBrush(brush);
|
||||
pen.setWidthF(stroke);
|
||||
q.setPen(pen);
|
||||
const auto rectf = QRectF(rect);
|
||||
const auto radius = std::min(rect.width(), rect.height()) / 2.;
|
||||
q.drawRoundedRect(
|
||||
rectf.marginsAdded(QMarginsF{ half, half, half, half }),
|
||||
radius,
|
||||
radius);
|
||||
q.setFont(font);
|
||||
q.setPen(st::premiumButtonFg);
|
||||
q.drawText(rect, Qt::AlignCenter, text);
|
||||
}
|
||||
q.end();
|
||||
|
||||
auto p = QPainter(overlay);
|
||||
p.drawImage(0, 0, state->layer);
|
||||
}, overlay->lifetime());
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -57,3 +57,8 @@ object_ptr<Ui::BoxContent> ReassignBoostsBox(
|
|||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<std::vector<not_null<PeerData*>>> from,
|
||||
not_null<PeerData*> to);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> CreateUserpicsWithMoreBadge(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<std::vector<not_null<PeerData*>>> peers,
|
||||
int limit);
|
||||
|
|
|
@ -1464,9 +1464,26 @@ not_null<Ui::GradientButton*> CreateSubscribeButton(
|
|||
SubscribeButtonArgs &&args) {
|
||||
Expects(args.show || args.controller);
|
||||
|
||||
if (!args.show && args.controller) {
|
||||
args.show = args.controller->uiShow();
|
||||
}
|
||||
auto show = args.show ? std::move(args.show) : args.controller->uiShow();
|
||||
auto resolve = [show](
|
||||
not_null<Main::Session*> session,
|
||||
ChatHelpers::WindowUsage usage) {
|
||||
Expects(session == &show->session());
|
||||
|
||||
return show->resolveWindow(usage);
|
||||
};
|
||||
return CreateSubscribeButton(
|
||||
std::move(show),
|
||||
std::move(resolve),
|
||||
std::move(args));
|
||||
}
|
||||
|
||||
not_null<Ui::GradientButton*> CreateSubscribeButton(
|
||||
std::shared_ptr<::Main::SessionShow> show,
|
||||
Fn<Window::SessionController*(
|
||||
not_null<::Main::Session*>,
|
||||
ChatHelpers::WindowUsage)> resolveWindow,
|
||||
SubscribeButtonArgs &&args) {
|
||||
const auto result = Ui::CreateChild<Ui::GradientButton>(
|
||||
args.parent.get(),
|
||||
args.gradientStops
|
||||
|
@ -1474,13 +1491,19 @@ not_null<Ui::GradientButton*> CreateSubscribeButton(
|
|||
: Ui::Premium::ButtonGradientStops());
|
||||
|
||||
result->setClickedCallback([
|
||||
show = args.show,
|
||||
show,
|
||||
resolveWindow,
|
||||
promo = args.showPromo,
|
||||
computeRef = args.computeRef,
|
||||
computeBotUrl = args.computeBotUrl] {
|
||||
const auto window = show->resolveWindow(
|
||||
const auto window = resolveWindow(
|
||||
&show->session(),
|
||||
ChatHelpers::WindowUsage::PremiumPromo);
|
||||
if (!window) {
|
||||
return;
|
||||
} else if (promo) {
|
||||
Settings::ShowPremium(window, computeRef());
|
||||
return;
|
||||
}
|
||||
const auto url = computeBotUrl ? computeBotUrl() : QString();
|
||||
if (!url.isEmpty()) {
|
||||
|
@ -1503,7 +1526,7 @@ not_null<Ui::GradientButton*> CreateSubscribeButton(
|
|||
const auto &st = st::premiumPreviewBox.button;
|
||||
result->resize(args.parent->width(), st.height);
|
||||
|
||||
const auto premium = &args.show->session().api().premium();
|
||||
const auto premium = &show->session().api().premium();
|
||||
premium->reload();
|
||||
const auto computeCost = [=] {
|
||||
const auto amount = premium->monthlyAmount();
|
||||
|
|
|
@ -79,6 +79,7 @@ struct SubscribeButtonArgs final {
|
|||
std::optional<QGradientStops> gradientStops;
|
||||
Fn<QString()> computeBotUrl; // nullable
|
||||
std::shared_ptr<ChatHelpers::Show> show;
|
||||
bool showPromo = false;
|
||||
};
|
||||
|
||||
|
||||
|
@ -91,6 +92,13 @@ struct SubscribeButtonArgs final {
|
|||
[[nodiscard]] not_null<Ui::GradientButton*> CreateSubscribeButton(
|
||||
SubscribeButtonArgs &&args);
|
||||
|
||||
[[nodiscard]] not_null<Ui::GradientButton*> CreateSubscribeButton(
|
||||
std::shared_ptr<::Main::SessionShow> show,
|
||||
Fn<Window::SessionController*(
|
||||
not_null<::Main::Session*>,
|
||||
ChatHelpers::WindowUsage)> resolveWindow,
|
||||
SubscribeButtonArgs &&args);
|
||||
|
||||
[[nodiscard]] std::vector<PremiumFeature> PremiumFeaturesOrder(
|
||||
not_null<::Main::Session*> session);
|
||||
|
||||
|
|
|
@ -52,7 +52,9 @@ constexpr auto kShowOrLineOpacity = 0.3;
|
|||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<RpWidget> MakeShowOrLabel(
|
||||
} // namespace
|
||||
|
||||
object_ptr<RpWidget> MakeShowOrLabel(
|
||||
not_null<RpWidget*> parent,
|
||||
rpl::producer<QString> text) {
|
||||
auto result = object_ptr<FlatLabel>(
|
||||
|
@ -80,8 +82,6 @@ constexpr auto kShowOrLineOpacity = 0.3;
|
|||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ShowOrPremiumBox(
|
||||
not_null<GenericBox*> box,
|
||||
ShowOrPremium type,
|
||||
|
|
|
@ -7,8 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/object_ptr.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class RpWidget;
|
||||
class GenericBox;
|
||||
|
||||
enum class ShowOrPremium : uchar {
|
||||
|
@ -22,4 +25,8 @@ void ShowOrPremiumBox(
|
|||
Fn<void()> justShow,
|
||||
Fn<void()> toPremium);
|
||||
|
||||
[[nodiscard]] object_ptr<RpWidget> MakeShowOrLabel(
|
||||
not_null<RpWidget*> parent,
|
||||
rpl::producer<QString> text);
|
||||
|
||||
} // namespace Ui
|
||||
|
|
Loading…
Add table
Reference in a new issue