| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039 |
- /*
- 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/add_participants_box.h"
- #include "api/api_chat_participants.h"
- #include "api/api_invite_links.h"
- #include "api/api_premium.h"
- #include "boxes/peers/edit_participant_box.h"
- #include "boxes/peers/edit_peer_type_box.h"
- #include "boxes/peers/replace_boost_box.h"
- #include "boxes/max_invite_box.h"
- #include "chat_helpers/message_field.h"
- #include "lang/lang_keys.h"
- #include "data/data_channel.h"
- #include "data/data_chat.h"
- #include "data/data_user.h"
- #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 "history/history_item_helpers.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,
- not_null<const style::PeerListItem*> lockSt,
- bool locked);
- PaintRoundImageCallback generatePaintUserpicCallback(
- bool forceRound) override;
- Api::MessageMoneyRestriction restriction() const;
- void setRestriction(Api::MessageMoneyRestriction restriction);
- void preloadUserpic() override;
- void paintUserpicOverlay(
- Painter &p,
- const style::PeerListItem &st,
- int x,
- int y,
- int outerWidth) override;
- bool refreshLock();
- private:
- struct Restriction {
- Api::MessageMoneyRestriction value;
- RestrictionBadgeCache cache;
- };
- const bool _locked = false;
- const not_null<const style::PeerListItem*> _lockSt;
- QImage _disabledFrame;
- InMemoryKey _userpicKey;
- int _paletteVersion = 0;
- std::shared_ptr<Restriction> _restriction;
- };
- class InviteForbiddenController final : public PeerListController {
- public:
- InviteForbiddenController(
- not_null<PeerData*> peer,
- ForbiddenInvites forbidden);
- Main::Session &session() const override;
- void prepare() override;
- void rowClicked(not_null<PeerListRow*> row) override;
- [[nodiscard]] bool canInvite() const {
- return _can;
- }
- [[nodiscard]] rpl::producer<int> selectedValue() const {
- return _selected.value();
- }
- [[nodiscard]] rpl::producer<int> starsToSend() const {
- return _starsToSend.value();
- }
- void send(
- std::vector<not_null<PeerData*>> list,
- Ui::ShowPtr show,
- Fn<void()> close);
- private:
- void appendRow(not_null<UserData*> user);
- [[nodiscard]] std::unique_ptr<ForbiddenRow> createRow(
- not_null<UserData*> user) const;
- [[nodiscard]] bool canInvite(not_null<PeerData*> peer) const;
- void send(
- std::vector<not_null<PeerData*>> list,
- Ui::ShowPtr show,
- Fn<void()> close,
- Api::SendOptions options);
- void setSimpleCover();
- void setComplexCover();
- const not_null<PeerData*> _peer;
- const ForbiddenInvites _forbidden;
- const std::vector<not_null<UserData*>> &_users;
- const bool _can = false;
- rpl::variable<int> _selected;
- rpl::variable<int> _starsToSend;
- bool _sending = false;
- rpl::lifetime _paymentCheckLifetime;
- };
- base::flat_set<not_null<UserData*>> GetAlreadyInFromPeer(PeerData *peer) {
- if (!peer) {
- return {};
- }
- if (const auto chat = peer->asChat()) {
- return chat->participants;
- } else if (const auto channel = peer->asChannel()) {
- if (channel->isMegagroup() && channel->canViewMembers()) {
- const auto &participants = channel->mgInfo->lastParticipants;
- return { participants.cbegin(), participants.cend() };
- }
- }
- 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,
- ForbiddenInvites forbidden)
- : _peer(peer)
- , _forbidden(std::move(forbidden))
- , _users(_forbidden.users)
- , _can(peer->isChat()
- ? peer->asChat()->canHaveInviteLink()
- : peer->asChannel()->canHaveInviteLink())
- , _selected(_can
- ? (int(_users.size()) - int(_forbidden.premiumAllowsWrite.size()))
- : 0) {
- }
- Main::Session &InviteForbiddenController::session() const {
- return _peer->session();
- }
- ForbiddenRow::ForbiddenRow(
- not_null<PeerData*> peer,
- not_null<const style::PeerListItem*> lockSt,
- bool locked)
- : PeerListRow(peer)
- , _locked(locked)
- , _lockSt(lockSt) {
- if (_locked) {
- setCustomStatus(tr::lng_invite_status_disabled(tr::now));
- } else {
- setRestriction(Api::ResolveMessageMoneyRestrictions(peer, nullptr));
- }
- }
- PaintRoundImageCallback ForbiddenRow::generatePaintUserpicCallback(
- bool forceRound) {
- const auto peer = this->peer();
- const auto saved = peer->isSelf();
- const auto replies = peer->isRepliesChat();
- const auto verifyCodes = peer->isVerifyCodes();
- auto userpic = (saved || replies || verifyCodes)
- ? 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
- && !verifyCodes
- && (_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);
- };
- }
- Api::MessageMoneyRestriction ForbiddenRow::restriction() const {
- return _restriction
- ? _restriction->value
- : Api::MessageMoneyRestriction();
- }
- void ForbiddenRow::setRestriction(Api::MessageMoneyRestriction restriction) {
- if (!restriction || !restriction.starsPerMessage) {
- _restriction = nullptr;
- return;
- } else if (!_restriction) {
- _restriction = std::make_unique<Restriction>();
- }
- _restriction->value = restriction;
- }
- void ForbiddenRow::paintUserpicOverlay(
- Painter &p,
- const style::PeerListItem &st,
- int x,
- int y,
- int outerWidth) {
- if (const auto &r = _restriction) {
- PaintRestrictionBadge(
- p,
- _lockSt,
- r->value.starsPerMessage,
- r->cache,
- x,
- y,
- outerWidth,
- st.photoSize);
- }
- }
- bool ForbiddenRow::refreshLock() {
- if (_locked) {
- return false;
- } else if (const auto user = peer()->asUser()) {
- using Restriction = Api::MessageMoneyRestriction;
- auto r = Api::ResolveMessageMoneyRestrictions(user, nullptr);
- if (!r || !r.starsPerMessage) {
- r = Restriction();
- }
- if ((_restriction ? _restriction->value : Restriction()) != r) {
- setRestriction(r);
- return true;
- }
- }
- return false;
- }
- void ForbiddenRow::preloadUserpic() {
- PeerListRow::preloadUserpic();
- const auto peer = this->peer();
- const auto known = Api::ResolveMessageMoneyRestrictions(
- peer,
- nullptr).known;
- if (known) {
- return;
- } else if (const auto user = peer->asUser()) {
- const auto api = &user->session().api();
- api->premium().resolveMessageMoneyRestrictions(user);
- } else if (const auto group = peer->asChannel()) {
- group->updateFull();
- }
- }
- 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
- ? tr::lng_via_link_cant_many
- : broadcast
- ? tr::lng_via_link_channel_many
- : tr::lng_via_link_group_many;
- const auto phraseNamed = !_can
- ? tr::lng_via_link_cant_one
- : broadcast
- ? tr::lng_via_link_channel_one
- : tr::lng_via_link_group_one;
- auto text = (count != 1)
- ? phraseCounted(
- lt_count,
- rpl::single<float64>(count),
- Ui::Text::RichLangValue)
- : phraseNamed(
- lt_user,
- rpl::single(TextWithEntities{ _users.front()->name() }),
- Ui::Text::RichLangValue);
- delegate()->peerListSetAboveWidget(object_ptr<Ui::PaddingWrap<>>(
- (QWidget*)nullptr,
- object_ptr<Ui::FlatLabel>(
- (QWidget*)nullptr,
- std::move(text),
- st::requestPeerRestriction),
- st::boxRowPadding));
- }
- 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() {
- session().api().premium().someMessageMoneyRestrictionsResolved(
- ) | rpl::start_with_next([=] {
- auto stars = 0;
- const auto process = [&](not_null<PeerListRow*> raw) {
- const auto row = static_cast<ForbiddenRow*>(raw.get());
- if (row->refreshLock()) {
- delegate()->peerListUpdateRow(raw);
- }
- if (const auto r = row->restriction()) {
- stars += r.starsPerMessage;
- }
- };
- auto count = delegate()->peerListFullRowsCount();
- for (auto i = 0; i != count; ++i) {
- process(delegate()->peerListRowAt(i));
- }
- _starsToSend = stars;
- count = delegate()->peerListSearchRowsCount();
- for (auto i = 0; i != count; ++i) {
- process(delegate()->peerListSearchRowAt(i));
- }
- }, lifetime());
- if (session().premium()
- || (_forbidden.premiumAllowsInvite.empty()
- && _forbidden.premiumAllowsWrite.empty())) {
- setSimpleCover();
- } else {
- setComplexCover();
- }
- for (const auto &user : _users) {
- appendRow(user);
- }
- 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 (!canInvite(row->peer())) {
- return;
- }
- const auto checked = row->checked();
- delegate()->peerListSetRowChecked(row, !checked);
- _selected = _selected.current() + (checked ? -1 : 1);
- const auto r = static_cast<ForbiddenRow*>(row.get())->restriction();
- if (r.starsPerMessage) {
- _starsToSend = _starsToSend.current()
- + (checked ? -r.starsPerMessage : r.starsPerMessage);
- }
- }
- void InviteForbiddenController::appendRow(not_null<UserData*> user) {
- if (!delegate()->peerListFindRow(user->id.value)) {
- auto row = createRow(user);
- const auto raw = row.get();
- delegate()->peerListAppendRow(std::move(row));
- if (canInvite(user)) {
- delegate()->peerListSetRowChecked(raw, true);
- if (const auto r = raw->restriction()) {
- _starsToSend = _starsToSend.current() + r.starsPerMessage;
- }
- }
- }
- }
- void InviteForbiddenController::send(
- std::vector<not_null<PeerData*>> list,
- Ui::ShowPtr show,
- Fn<void()> close) {
- send(list, show, close, {});
- }
- void InviteForbiddenController::send(
- std::vector<not_null<PeerData*>> list,
- Ui::ShowPtr show,
- Fn<void()> close,
- Api::SendOptions options) {
- if (list.empty()) {
- return;
- }
- _paymentCheckLifetime.destroy();
- const auto withPaymentApproved = [=](int approved) {
- auto copy = options;
- copy.starsApproved = approved;
- send(list, show, close, copy);
- };
- const auto messagesCount = 1;
- const auto alreadyApproved = options.starsApproved;
- auto paid = std::vector<not_null<PeerData*>>();
- auto waiting = base::flat_set<not_null<PeerData*>>();
- auto totalStars = 0;
- for (const auto &peer : list) {
- const auto details = ComputePaymentDetails(peer, messagesCount);
- if (!details) {
- waiting.emplace(peer);
- } else if (details->stars > 0) {
- totalStars += details->stars;
- paid.push_back(peer);
- }
- }
- if (!waiting.empty()) {
- session().changes().peerUpdates(
- Data::PeerUpdate::Flag::FullInfo
- ) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
- if (waiting.contains(update.peer)) {
- withPaymentApproved(alreadyApproved);
- }
- }, _paymentCheckLifetime);
- if (!session().credits().loaded()) {
- session().credits().loadedValue(
- ) | rpl::filter(
- rpl::mappers::_1
- ) | rpl::take(1) | rpl::start_with_next([=] {
- withPaymentApproved(alreadyApproved);
- }, _paymentCheckLifetime);
- }
- return;
- } else if (totalStars > alreadyApproved) {
- const auto sessionShow = Main::MakeSessionShow(show, &session());
- ShowSendPaidConfirm(sessionShow, paid, SendPaymentDetails{
- .messages = messagesCount,
- .stars = totalStars,
- }, [=] { withPaymentApproved(totalStars); });
- return;
- } else if (_sending) {
- return;
- }
- _sending = true;
- const auto chat = _peer->asChat();
- const auto channel = _peer->asChannel();
- const auto sendLink = [=] {
- const auto link = chat ? chat->inviteLink() : channel->inviteLink();
- if (link.isEmpty()) {
- return false;
- }
- auto full = options;
- auto &api = _peer->session().api();
- for (const auto &to : list) {
- auto copy = full;
- copy.starsApproved = std::min(
- to->starsPerMessageChecked(),
- full.starsApproved);
- full.starsApproved -= copy.starsApproved;
- const auto history = to->owner().history(to);
- auto message = Api::MessageToSend(
- Api::SendAction(history, copy));
- message.textWithTags = { link };
- message.action.clearDraft = false;
- api.sendMessage(std::move(message));
- }
- auto text = (list.size() == 1)
- ? tr::lng_via_link_shared_one(
- tr::now,
- lt_user,
- TextWithEntities{ list.front()->name() },
- Ui::Text::RichLangValue)
- : tr::lng_via_link_shared_many(
- tr::now,
- lt_count,
- int(list.size()),
- Ui::Text::RichLangValue);
- close();
- show->showToast(std::move(text));
- return true;
- };
- const auto sendForFull = [=] {
- if (!sendLink()) {
- _peer->session().api().inviteLinks().create({
- _peer,
- [=](auto) {
- if (!sendLink()) {
- close();
- }
- },
- });
- }
- };
- if (_peer->isFullLoaded()) {
- sendForFull();
- } else if (!sendLink()) {
- _peer->session().api().requestFullPeer(_peer);
- _peer->session().changes().peerUpdates(
- _peer,
- Data::PeerUpdate::Flag::FullInfo
- ) | rpl::start_with_next([=] {
- sendForFull();
- }, lifetime());
- }
- }
- std::unique_ptr<ForbiddenRow> InviteForbiddenController::createRow(
- not_null<UserData*> user) const {
- const auto locked = _can && !canInvite(user);
- const auto lockSt = &computeListSt().item;
- return std::make_unique<ForbiddenRow>(user, lockSt, locked);
- }
- } // namespace
- AddParticipantsBoxController::AddParticipantsBoxController(
- not_null<Main::Session*> session)
- : ContactsBoxController(session) {
- }
- AddParticipantsBoxController::AddParticipantsBoxController(
- not_null<PeerData*> peer)
- : AddParticipantsBoxController(
- peer,
- GetAlreadyInFromPeer(peer)) {
- }
- AddParticipantsBoxController::AddParticipantsBoxController(
- not_null<PeerData*> peer,
- base::flat_set<not_null<UserData*>> &&alreadyIn)
- : ContactsBoxController(&peer->session())
- , _peer(peer)
- , _alreadyIn(std::move(alreadyIn)) {
- if (needsInviteLinkButton()) {
- setStyleOverrides(&st::peerListWithInviteViaLink);
- }
- subscribeToMigration();
- }
- void AddParticipantsBoxController::subscribeToMigration() {
- Expects(_peer != nullptr);
- SubscribeToMigration(
- _peer,
- lifetime(),
- [=](not_null<ChannelData*> channel) { _peer = channel; });
- }
- void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
- const auto moneyRestrictionError = WriteMoneyRestrictionError;
- if (RecipientRow::ShowLockedError(this, row, moneyRestrictionError)) {
- return;
- }
- const auto &serverConfig = session().serverConfig();
- auto count = fullCount();
- auto limit = _peer && (_peer->isChat() || _peer->isMegagroup())
- ? serverConfig.megagroupSizeMax
- : serverConfig.chatSizeMax;
- if (count < limit || row->checked()) {
- delegate()->peerListSetRowChecked(row, !row->checked());
- updateTitle();
- } else if (const auto channel = _peer ? _peer->asChannel() : nullptr) {
- if (!_peer->isMegagroup()) {
- showBox(Box<MaxInviteBox>(_peer->asChannel()));
- }
- } else if (count >= serverConfig.chatSizeMax
- && count < serverConfig.megagroupSizeMax) {
- showBox(Ui::MakeInformBox(tr::lng_profile_add_more_after_create()));
- }
- }
- void AddParticipantsBoxController::itemDeselectedHook(
- not_null<PeerData*> peer) {
- updateTitle();
- }
- void AddParticipantsBoxController::prepareViewHook() {
- updateTitle();
- TrackMessageMoneyRestrictionsChanges(this, lifetime());
- }
- int AddParticipantsBoxController::alreadyInCount() const {
- if (!_peer) {
- return 1; // self
- }
- if (const auto chat = _peer->asChat()) {
- return qMax(chat->count, 1);
- } else if (const auto channel = _peer->asChannel()) {
- return qMax(channel->membersCount(), int(_alreadyIn.size()));
- }
- Unexpected("User in AddParticipantsBoxController::alreadyInCount");
- }
- bool AddParticipantsBoxController::isAlreadyIn(
- not_null<UserData*> user) const {
- if (!_peer) {
- return false;
- }
- if (const auto chat = _peer->asChat()) {
- return _alreadyIn.contains(user)
- || chat->participants.contains(user);
- } else if (const auto channel = _peer->asChannel()) {
- return _alreadyIn.contains(user)
- || (channel->isMegagroup()
- && channel->canViewMembers()
- && base::contains(channel->mgInfo->lastParticipants, user));
- }
- Unexpected("User in AddParticipantsBoxController::isAlreadyIn");
- }
- int AddParticipantsBoxController::fullCount() const {
- return alreadyInCount() + delegate()->peerListSelectedRowsCount();
- }
- std::unique_ptr<PeerListRow> AddParticipantsBoxController::createRow(
- not_null<UserData*> user) {
- if (user->isSelf()) {
- return nullptr;
- }
- const auto already = isAlreadyIn(user);
- const auto maybeLockedSt = already ? nullptr : &computeListSt().item;
- auto result = std::make_unique<RecipientRow>(user, maybeLockedSt);
- if (already) {
- result->setDisabledState(PeerListRow::State::DisabledChecked);
- }
- return result;
- }
- void AddParticipantsBoxController::updateTitle() {
- const auto additional = (_peer
- && _peer->isChannel()
- && !_peer->isMegagroup())
- ? QString()
- : (u"%1 / %2"_q
- ).arg(fullCount()
- ).arg(session().serverConfig().megagroupSizeMax);
- delegate()->peerListSetTitle(tr::lng_profile_add_participant());
- delegate()->peerListSetAdditionalTitle(rpl::single(additional));
- addInviteLinkButton();
- }
- bool AddParticipantsBoxController::needsInviteLinkButton() {
- if (!_peer) {
- return false;
- } else if (const auto channel = _peer->asChannel()) {
- return channel->canHaveInviteLink();
- }
- return _peer->asChat()->canHaveInviteLink();
- }
- QPointer<Ui::BoxContent> AddParticipantsBoxController::showBox(
- object_ptr<Ui::BoxContent> box) const {
- const auto weak = Ui::MakeWeak(box.data());
- delegate()->peerListUiShow()->showBox(std::move(box));
- return weak;
- }
- void AddParticipantsBoxController::addInviteLinkButton() {
- if (!needsInviteLinkButton()) {
- return;
- }
- auto button = object_ptr<Ui::PaddingWrap<Ui::SettingsButton>>(
- nullptr,
- object_ptr<Ui::SettingsButton>(
- nullptr,
- tr::lng_profile_add_via_link(),
- st::inviteViaLinkButton),
- style::margins(0, st::membersMarginTop, 0, 0));
- const auto icon = Ui::CreateChild<Info::Profile::FloatingIcon>(
- button->entity(),
- st::inviteViaLinkIcon,
- QPoint());
- button->entity()->heightValue(
- ) | rpl::start_with_next([=](int height) {
- icon->moveToLeft(
- st::inviteViaLinkIconPosition.x(),
- (height - st::inviteViaLinkIcon.height()) / 2);
- }, icon->lifetime());
- button->entity()->setClickedCallback([=] {
- showBox(Box<EditPeerTypeBox>(_peer));
- });
- button->entity()->events(
- ) | rpl::filter([=](not_null<QEvent*> e) {
- return (e->type() == QEvent::Enter);
- }) | rpl::start_with_next([=] {
- delegate()->peerListMouseLeftGeometry();
- }, button->lifetime());
- delegate()->peerListSetAboveWidget(std::move(button));
- delegate()->peerListRefreshRows();
- }
- void AddParticipantsBoxController::inviteSelectedUsers(
- not_null<PeerListBox*> box,
- Fn<void()> done) const {
- Expects(_peer != nullptr);
- const auto rows = box->collectSelectedRows();
- const auto users = ranges::views::all(
- rows
- ) | ranges::views::transform([](not_null<PeerData*> peer) {
- Expects(peer->isUser());
- Expects(!peer->isSelf());
- return not_null<UserData*>(peer->asUser());
- }) | ranges::to_vector;
- if (users.empty()) {
- return;
- }
- const auto show = box->uiShow();
- const auto request = [=](bool checked) {
- _peer->session().api().chatParticipants().add(
- show,
- _peer,
- users,
- checked);
- };
- if (_peer->isChannel()) {
- request(false);
- return done();
- }
- show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
- auto checkbox = object_ptr<Ui::Checkbox>(
- box.get(),
- tr::lng_participant_invite_history(),
- true,
- st::defaultBoxCheckbox);
- const auto weak = Ui::MakeWeak(checkbox.data());
- auto text = (users.size() == 1)
- ? tr::lng_participant_invite_sure(
- tr::now,
- lt_user,
- { users.front()->name()},
- lt_group,
- { _peer->name()},
- Ui::Text::RichLangValue)
- : tr::lng_participant_invite_sure_many(
- tr::now,
- lt_count,
- int(users.size()),
- lt_group,
- { _peer->name() },
- Ui::Text::RichLangValue);
- Ui::ConfirmBox(box, {
- .text = std::move(text),
- .confirmed = crl::guard(weak, [=](Fn<void()> &&close) {
- request(weak->checked());
- done();
- close();
- }),
- .confirmText = tr::lng_participant_invite(),
- });
- auto padding = st::boxPadding;
- padding.setTop(padding.bottom());
- box->addRow(std::move(checkbox), std::move(padding));
- }));
- }
- void AddParticipantsBoxController::Start(
- not_null<Window::SessionNavigation*> navigation,
- not_null<ChatData*> chat) {
- auto controller = std::make_unique<AddParticipantsBoxController>(chat);
- const auto weak = controller.get();
- const auto parent = navigation->parentController();
- auto initBox = [=](not_null<PeerListBox*> box) {
- box->addButton(tr::lng_participant_invite(), [=] {
- weak->inviteSelectedUsers(box, [=] {
- parent->showPeerHistory(
- chat,
- Window::SectionShow::Way::ClearStack,
- ShowAtTheEndMsgId);
- });
- });
- box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
- };
- parent->show(
- Box<PeerListBox>(std::move(controller), std::move(initBox)));
- }
- void AddParticipantsBoxController::Start(
- not_null<Window::SessionNavigation*> navigation,
- not_null<ChannelData*> channel,
- base::flat_set<not_null<UserData*>> &&alreadyIn,
- bool justCreated) {
- auto controller = std::make_unique<AddParticipantsBoxController>(
- channel,
- std::move(alreadyIn));
- const auto weak = controller.get();
- const auto parent = navigation->parentController();
- auto initBox = [=](not_null<PeerListBox*> box) {
- box->addButton(tr::lng_participant_invite(), [=] {
- weak->inviteSelectedUsers(box, [=] {
- if (channel->isMegagroup()) {
- parent->showPeerHistory(
- channel,
- Window::SectionShow::Way::ClearStack,
- ShowAtTheEndMsgId);
- } else {
- box->closeBox();
- }
- });
- });
- box->addButton(
- justCreated ? tr::lng_create_group_skip() : tr::lng_cancel(),
- [=] { box->closeBox(); });
- if (justCreated) {
- const auto weak = base::make_weak(parent);
- box->boxClosing() | rpl::start_with_next([=] {
- auto params = Window::SectionShow();
- params.activation = anim::activation::background;
- if (const auto strong = weak.get()) {
- strong->showPeerHistory(
- channel,
- params,
- ShowAtTheEndMsgId);
- }
- }, box->lifetime());
- }
- };
- parent->show(
- Box<PeerListBox>(std::move(controller), std::move(initBox)));
- }
- void AddParticipantsBoxController::Start(
- not_null<Window::SessionNavigation*> navigation,
- not_null<ChannelData*> channel,
- base::flat_set<not_null<UserData*>> &&alreadyIn) {
- Start(navigation, channel, std::move(alreadyIn), false);
- }
- void AddParticipantsBoxController::Start(
- not_null<Window::SessionNavigation*> navigation,
- not_null<ChannelData*> channel) {
- Start(navigation, channel, {}, true);
- }
- ForbiddenInvites CollectForbiddenUsers(
- not_null<Main::Session*> session,
- const MTPmessages_InvitedUsers &result) {
- const auto &data = result.data();
- const auto owner = &session->data();
- auto forbidden = ForbiddenInvites();
- for (const auto &missing : data.vmissing_invitees().v) {
- const auto &data = missing.data();
- const auto user = owner->userLoaded(data.vuser_id());
- if (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;
- }
- bool ChatInviteForbidden(
- std::shared_ptr<Ui::Show> show,
- not_null<PeerData*> peer,
- 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,
- std::move(forbidden));
- const auto weak = controller.get();
- auto initBox = [=](not_null<PeerListBox*> box) {
- const auto can = weak->canInvite();
- if (!can) {
- box->addButton(tr::lng_close(), [=] {
- box->closeBox();
- });
- return;
- }
- weak->selectedValue(
- ) | rpl::map(
- rpl::mappers::_1 > 0
- ) | rpl::distinct_until_changed(
- ) | rpl::start_with_next([=](bool has) {
- box->clearButtons();
- if (has) {
- const auto send = box->addButton(tr::lng_via_link_send(), [=] {
- weak->send(
- box->collectSelectedRows(),
- box->uiShow(),
- crl::guard(box, [=] { box->closeBox(); }));
- });
- send->setText(PaidSendButtonText(
- weak->starsToSend(),
- tr::lng_via_link_send()));
- }
- box->addButton(tr::lng_create_group_skip(), [=] {
- 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)));
- return true;
- }
- AddSpecialBoxController::AddSpecialBoxController(
- not_null<PeerData*> peer,
- Role role,
- AdminDoneCallback adminDoneCallback,
- BannedDoneCallback bannedDoneCallback)
- : PeerListController(std::make_unique<AddSpecialBoxSearchController>(
- peer,
- &_additional))
- , _peer(peer)
- , _api(&_peer->session().mtp())
- , _role(role)
- , _additional(peer, Role::Members)
- , _adminDoneCallback(std::move(adminDoneCallback))
- , _bannedDoneCallback(std::move(bannedDoneCallback)) {
- subscribeToMigration();
- }
- Main::Session &AddSpecialBoxController::session() const {
- return _peer->session();
- }
- void AddSpecialBoxController::subscribeToMigration() {
- const auto chat = _peer->asChat();
- if (!chat) {
- return;
- }
- SubscribeToMigration(
- chat,
- lifetime(),
- [=](not_null<ChannelData*> channel) { migrate(chat, channel); });
- }
- void AddSpecialBoxController::migrate(
- not_null<ChatData*> chat,
- not_null<ChannelData*> channel) {
- _peer = channel;
- _additional.migrate(chat, channel);
- }
- QPointer<Ui::BoxContent> AddSpecialBoxController::showBox(
- object_ptr<Ui::BoxContent> box) const {
- const auto weak = Ui::MakeWeak(box.data());
- delegate()->peerListUiShow()->showBox(std::move(box));
- return weak;
- }
- std::unique_ptr<PeerListRow> AddSpecialBoxController::createSearchRow(
- not_null<PeerData*> peer) {
- if (_excludeSelf && peer->isSelf()) {
- return nullptr;
- }
- if (const auto user = peer->asUser()) {
- return createRow(user);
- }
- return nullptr;
- }
- void AddSpecialBoxController::prepare() {
- delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
- auto title = [&] {
- switch (_role) {
- case Role::Members:
- return tr::lng_profile_participants_section();
- case Role::Admins:
- return tr::lng_channel_add_admin();
- case Role::Restricted:
- return tr::lng_channel_add_exception();
- case Role::Kicked:
- return tr::lng_channel_add_removed();
- }
- Unexpected("Role in AddSpecialBoxController::prepare()");
- }();
- delegate()->peerListSetTitle(std::move(title));
- setDescriptionText(tr::lng_contacts_loading(tr::now));
- setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now));
- if (const auto chat = _peer->asChat()) {
- prepareChatRows(chat);
- } else {
- loadMoreRows();
- }
- delegate()->peerListRefreshRows();
- }
- void AddSpecialBoxController::prepareChatRows(not_null<ChatData*> chat) {
- _onlineSorter = std::make_unique<ParticipantsOnlineSorter>(
- chat,
- delegate());
- rebuildChatRows(chat);
- if (!delegate()->peerListFullRowsCount()) {
- chat->updateFullForced();
- }
- using UpdateFlag = Data::PeerUpdate::Flag;
- chat->session().changes().peerUpdates(
- chat,
- UpdateFlag::Members | UpdateFlag::Admins
- ) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
- _additional.fillFromPeer();
- if (update.flags & UpdateFlag::Members) {
- rebuildChatRows(chat);
- }
- }, lifetime());
- }
- void AddSpecialBoxController::rebuildChatRows(not_null<ChatData*> chat) {
- if (chat->participants.empty()) {
- // We get such updates often
- // (when participants list was invalidated).
- //while (delegate()->peerListFullRowsCount() > 0) {
- // delegate()->peerListRemoveRow(
- // delegate()->peerListRowAt(0));
- //}
- return;
- }
- auto &participants = chat->participants;
- auto count = delegate()->peerListFullRowsCount();
- for (auto i = 0; i != count;) {
- auto row = delegate()->peerListRowAt(i);
- Assert(row->peer()->isUser());
- auto user = row->peer()->asUser();
- if (participants.contains(user)) {
- ++i;
- } else {
- delegate()->peerListRemoveRow(row);
- --count;
- }
- }
- for (const auto &user : participants) {
- if (auto row = createRow(user)) {
- delegate()->peerListAppendRow(std::move(row));
- }
- }
- _onlineSorter->sort();
- delegate()->peerListRefreshRows();
- setDescriptionText(QString());
- }
- void AddSpecialBoxController::loadMoreRows() {
- if (searchController() && searchController()->loadMoreRows()) {
- return;
- } else if (!_peer->isChannel() || _loadRequestId || _allLoaded) {
- return;
- }
- // First query is small and fast, next loads a lot of rows.
- const auto perPage = (_offset > 0)
- ? kParticipantsPerPage
- : kParticipantsFirstPageCount;
- const auto participantsHash = uint64(0);
- const auto channel = _peer->asChannel();
- _loadRequestId = _api.request(MTPchannels_GetParticipants(
- channel->inputChannel,
- MTP_channelParticipantsRecent(),
- MTP_int(_offset),
- MTP_int(perPage),
- MTP_long(participantsHash)
- )).done([=](const MTPchannels_ChannelParticipants &result) {
- _loadRequestId = 0;
- result.match([&](const MTPDchannels_channelParticipants &data) {
- const auto &[availableCount, list] = Api::ChatParticipants::Parse(
- channel,
- data);
- for (const auto &data : list) {
- if (const auto participant = _additional.applyParticipant(
- data)) {
- appendRow(participant);
- }
- }
- if (const auto size = list.size()) {
- _offset += size;
- } else {
- // To be sure - wait for a whole empty result list.
- _allLoaded = true;
- }
- }, [&](const MTPDchannels_channelParticipantsNotModified &) {
- LOG(("API Error: channels.channelParticipantsNotModified received!"));
- });
- if (delegate()->peerListFullRowsCount() > 0) {
- setDescriptionText(QString());
- } else if (_allLoaded) {
- setDescriptionText(tr::lng_blocked_list_not_found(tr::now));
- }
- delegate()->peerListRefreshRows();
- }).fail([this] {
- _loadRequestId = 0;
- }).send();
- }
- void AddSpecialBoxController::rowClicked(not_null<PeerListRow*> row) {
- const auto participant = row->peer();
- const auto user = participant->asUser();
- switch (_role) {
- case Role::Admins:
- Assert(user != nullptr);
- return showAdmin(user);
- case Role::Restricted:
- Assert(user != nullptr);
- return showRestricted(user);
- case Role::Kicked: return kickUser(participant);
- }
- Unexpected("Role in AddSpecialBoxController::rowClicked()");
- }
- template <typename Callback>
- bool AddSpecialBoxController::checkInfoLoaded(
- not_null<PeerData*> participant,
- Callback callback) {
- if (_additional.infoLoaded(participant)) {
- return true;
- }
- // We don't know what this user status is in the group.
- const auto channel = _peer->asChannel();
- _api.request(MTPchannels_GetParticipant(
- channel->inputChannel,
- participant->input
- )).done([=](const MTPchannels_ChannelParticipant &result) {
- result.match([&](const MTPDchannels_channelParticipant &data) {
- channel->owner().processUsers(data.vusers());
- _additional.applyParticipant(
- Api::ChatParticipant(data.vparticipant(), channel));
- });
- callback();
- }).fail([=] {
- _additional.setExternal(participant);
- callback();
- }).send();
- return false;
- }
- void AddSpecialBoxController::showAdmin(
- not_null<UserData*> user,
- bool sure) {
- if (!checkInfoLoaded(user, [=] { showAdmin(user); })) {
- return;
- }
- _editBox = nullptr;
- if (_editParticipantBox) {
- _editParticipantBox->closeBox();
- }
- const auto chat = _peer->asChat();
- const auto channel = _peer->asChannel();
- const auto showAdminSure = crl::guard(this, [=] {
- showAdmin(user, true);
- });
- // Check restrictions.
- const auto canAddMembers = chat
- ? chat->canAddMembers()
- : channel->canAddMembers();
- const auto canBanMembers = chat
- ? chat->canBanMembers()
- : channel->canBanMembers();
- const auto adminRights = _additional.adminRights(user);
- if (adminRights.has_value()) {
- // The user is already an admin.
- } else if (_additional.isKicked(user)) {
- // The user is banned.
- if (canAddMembers) {
- if (canBanMembers) {
- if (!sure) {
- _editBox = showBox(
- Ui::MakeConfirmBox({
- tr::lng_sure_add_admin_unremove(),
- showAdminSure
- }));
- return;
- }
- } else {
- showBox(
- Ui::MakeInformBox(tr::lng_error_cant_add_admin_unban()));
- return;
- }
- } else {
- showBox(Ui::MakeInformBox(tr::lng_error_cant_add_admin_invite()));
- return;
- }
- } else if (_additional.restrictedRights(user).has_value()) {
- // The user is restricted.
- if (canBanMembers) {
- if (!sure) {
- _editBox = showBox(
- Ui::MakeConfirmBox({
- tr::lng_sure_add_admin_unremove(),
- showAdminSure
- }));
- return;
- }
- } else {
- showBox(Ui::MakeInformBox(tr::lng_error_cant_add_admin_unban()));
- return;
- }
- } else if (_additional.isExternal(user)) {
- // The user is not in the group yet.
- if (canAddMembers) {
- if (!sure) {
- auto text = ((_peer->isChat() || _peer->isMegagroup())
- ? tr::lng_sure_add_admin_invite
- : tr::lng_sure_add_admin_invite_channel)();
- _editBox = showBox(
- Ui::MakeConfirmBox({
- std::move(text),
- showAdminSure
- }));
- return;
- }
- } else {
- showBox(Ui::MakeInformBox(tr::lng_error_cant_add_admin_invite()));
- return;
- }
- }
- // Finally show the admin.
- const auto currentRights = adminRights
- ? *adminRights
- : ChatAdminRightsInfo();
- auto box = Box<EditAdminBox>(
- _peer,
- user,
- currentRights,
- _additional.adminRank(user),
- _additional.adminPromotedSince(user),
- _additional.adminPromotedBy(user));
- const auto show = delegate()->peerListUiShow();
- if (_additional.canAddOrEditAdmin(user)) {
- const auto done = crl::guard(this, [=](
- ChatAdminRightsInfo newRights,
- const QString &rank) {
- editAdminDone(user, newRights, rank);
- });
- const auto fail = crl::guard(this, [=] {
- if (_editParticipantBox) {
- _editParticipantBox->closeBox();
- }
- });
- box->setSaveCallback(
- SaveAdminCallback(show, _peer, user, done, fail));
- }
- _editParticipantBox = showBox(std::move(box));
- }
- void AddSpecialBoxController::editAdminDone(
- not_null<UserData*> user,
- ChatAdminRightsInfo rights,
- const QString &rank) {
- if (_editParticipantBox) {
- _editParticipantBox->closeBox();
- }
- _additional.applyAdminLocally(user, rights, rank);
- if (const auto callback = _adminDoneCallback) {
- callback(user, rights, rank);
- }
- }
- void AddSpecialBoxController::showRestricted(
- not_null<UserData*> user,
- bool sure) {
- if (!checkInfoLoaded(user, [=] { showRestricted(user); })) {
- return;
- }
- _editBox = nullptr;
- if (_editParticipantBox) {
- _editParticipantBox->closeBox();
- }
- const auto showRestrictedSure = crl::guard(this, [=] {
- showRestricted(user, true);
- });
- // Check restrictions.
- const auto restrictedRights = _additional.restrictedRights(user);
- if (restrictedRights.has_value()) {
- // The user is already banned or restricted.
- } else if (_additional.adminRights(user).has_value()
- || _additional.isCreator(user)) {
- // The user is an admin or creator.
- if (!_additional.isCreator(user) && _additional.canEditAdmin(user)) {
- if (!sure) {
- _editBox = showBox(
- Ui::MakeConfirmBox({
- tr::lng_sure_ban_admin(),
- showRestrictedSure
- }));
- return;
- }
- } else {
- showBox(Ui::MakeInformBox(tr::lng_error_cant_ban_admin()));
- return;
- }
- }
- // Finally edit the restricted.
- const auto currentRights = restrictedRights
- ? *restrictedRights
- : ChatRestrictionsInfo();
- auto box = Box<EditRestrictedBox>(
- _peer,
- user,
- _additional.adminRights(user).has_value(),
- currentRights,
- _additional.restrictedBy(user),
- _additional.restrictedSince(user));
- if (_additional.canRestrictParticipant(user)) {
- const auto done = crl::guard(this, [=](
- ChatRestrictionsInfo newRights) {
- editRestrictedDone(user, newRights);
- });
- const auto fail = crl::guard(this, [=] {
- if (_editParticipantBox) {
- _editParticipantBox->closeBox();
- }
- });
- box->setSaveCallback(
- SaveRestrictedCallback(_peer, user, done, fail));
- }
- _editParticipantBox = showBox(std::move(box));
- }
- void AddSpecialBoxController::editRestrictedDone(
- not_null<PeerData*> participant,
- ChatRestrictionsInfo rights) {
- if (_editParticipantBox) {
- _editParticipantBox->closeBox();
- }
- _additional.applyBannedLocally(participant, rights);
- if (const auto callback = _bannedDoneCallback) {
- callback(participant, rights);
- }
- }
- void AddSpecialBoxController::kickUser(
- not_null<PeerData*> participant,
- bool sure) {
- if (!checkInfoLoaded(participant, [=] { kickUser(participant); })) {
- return;
- }
- const auto kickUserSure = crl::guard(this, [=] {
- kickUser(participant, true);
- });
- // Check restrictions.
- const auto user = participant->asUser();
- if (user && (_additional.adminRights(user).has_value()
- || (_additional.isCreator(user)))) {
- // The user is an admin or creator.
- if (!_additional.isCreator(user) && _additional.canEditAdmin(user)) {
- if (!sure) {
- _editBox = showBox(
- Ui::MakeConfirmBox({
- tr::lng_sure_ban_admin(),
- kickUserSure
- }));
- return;
- }
- } else {
- showBox(Ui::MakeInformBox(tr::lng_error_cant_ban_admin()));
- return;
- }
- }
- // Finally kick him.
- if (!sure) {
- const auto text = ((_peer->isChat() || _peer->isMegagroup())
- ? tr::lng_profile_sure_kick
- : tr::lng_profile_sure_kick_channel)(
- tr::now,
- lt_user,
- participant->name());
- _editBox = showBox(Ui::MakeConfirmBox({ text, kickUserSure }));
- return;
- }
- const auto restrictedRights = _additional.restrictedRights(participant);
- const auto currentRights = restrictedRights
- ? *restrictedRights
- : ChatRestrictionsInfo();
- const auto done = crl::guard(this, [=](
- ChatRestrictionsInfo newRights) {
- editRestrictedDone(participant, newRights);
- });
- const auto fail = crl::guard(this, [=] {
- _editBox = nullptr;
- });
- const auto callback = SaveRestrictedCallback(
- _peer,
- participant,
- done,
- fail);
- callback(currentRights, ChannelData::KickedRestrictedRights(participant));
- }
- bool AddSpecialBoxController::appendRow(not_null<PeerData*> participant) {
- if (delegate()->peerListFindRow(participant->id.value)
- || (_excludeSelf && participant->isSelf())) {
- return false;
- }
- delegate()->peerListAppendRow(createRow(participant));
- return true;
- }
- bool AddSpecialBoxController::prependRow(not_null<UserData*> user) {
- if (delegate()->peerListFindRow(user->id.value)) {
- return false;
- }
- delegate()->peerListPrependRow(createRow(user));
- return true;
- }
- std::unique_ptr<PeerListRow> AddSpecialBoxController::createRow(
- not_null<PeerData*> participant) const {
- return std::make_unique<PeerListRow>(participant);
- }
- AddSpecialBoxSearchController::AddSpecialBoxSearchController(
- not_null<PeerData*> peer,
- not_null<ParticipantsAdditionalData*> additional)
- : _peer(peer)
- , _additional(additional)
- , _api(&_peer->session().mtp())
- , _timer([=] { searchOnServer(); }) {
- subscribeToMigration();
- }
- void AddSpecialBoxSearchController::subscribeToMigration() {
- SubscribeToMigration(
- _peer,
- lifetime(),
- [=](not_null<ChannelData*> channel) { _peer = channel; });
- }
- void AddSpecialBoxSearchController::searchQuery(const QString &query) {
- if (_query != query) {
- _query = query;
- _offset = 0;
- _requestId = 0;
- _participantsLoaded = false;
- _chatsContactsAdded = false;
- _chatMembersAdded = false;
- _globalLoaded = false;
- if (!_query.isEmpty() && !searchParticipantsInCache()) {
- _timer.callOnce(AutoSearchTimeout);
- } else {
- _timer.cancel();
- }
- }
- }
- void AddSpecialBoxSearchController::searchOnServer() {
- Expects(!_query.isEmpty());
- loadMoreRows();
- }
- bool AddSpecialBoxSearchController::isLoading() {
- return _timer.isActive() || _requestId;
- }
- bool AddSpecialBoxSearchController::searchParticipantsInCache() {
- const auto i = _participantsCache.find(_query);
- if (i != _participantsCache.cend()) {
- _requestId = 0;
- searchParticipantsDone(
- _requestId,
- i->second.result,
- i->second.requestedCount);
- return true;
- }
- return false;
- }
- bool AddSpecialBoxSearchController::searchGlobalInCache() {
- auto it = _globalCache.find(_query);
- if (it != _globalCache.cend()) {
- _requestId = 0;
- searchGlobalDone(_requestId, it->second);
- return true;
- }
- return false;
- }
- bool AddSpecialBoxSearchController::loadMoreRows() {
- if (_query.isEmpty()) {
- return false;
- }
- if (_globalLoaded) {
- return true;
- }
- if (_participantsLoaded || _chatMembersAdded) {
- if (!_chatsContactsAdded) {
- addChatsContacts();
- }
- if (!isLoading() && !searchGlobalInCache()) {
- requestGlobal();
- }
- } else if (const auto chat = _peer->asChat()) {
- if (!_chatMembersAdded) {
- addChatMembers(chat);
- }
- } else if (!isLoading()) {
- requestParticipants();
- }
- return true;
- }
- void AddSpecialBoxSearchController::requestParticipants() {
- Expects(_peer->isChannel());
- // For search we request a lot of rows from the first query.
- // (because we've waited for search request by timer already,
- // so we don't expect it to be fast, but we want to fill cache).
- const auto perPage = kParticipantsPerPage;
- const auto participantsHash = uint64(0);
- const auto channel = _peer->asChannel();
- _requestId = _api.request(MTPchannels_GetParticipants(
- channel->inputChannel,
- MTP_channelParticipantsSearch(MTP_string(_query)),
- MTP_int(_offset),
- MTP_int(perPage),
- MTP_long(participantsHash)
- )).done([=](
- const MTPchannels_ChannelParticipants &result,
- mtpRequestId requestId) {
- searchParticipantsDone(requestId, result, perPage);
- }).fail([=](const MTP::Error &error, mtpRequestId requestId) {
- if (_requestId == requestId) {
- _requestId = 0;
- _participantsLoaded = true;
- loadMoreRows();
- delegate()->peerListSearchRefreshRows();
- }
- }).send();
- auto entry = Query();
- entry.text = _query;
- entry.offset = _offset;
- _participantsQueries.emplace(_requestId, entry);
- }
- void AddSpecialBoxSearchController::searchParticipantsDone(
- mtpRequestId requestId,
- const MTPchannels_ChannelParticipants &result,
- int requestedCount) {
- Expects(_peer->isChannel());
- const auto channel = _peer->asChannel();
- auto query = _query;
- if (requestId) {
- const auto addToCache = [&] {
- auto it = _participantsQueries.find(requestId);
- if (it != _participantsQueries.cend()) {
- query = it->second.text;
- if (it->second.offset == 0) {
- auto &entry = _participantsCache[query];
- entry.result = result;
- entry.requestedCount = requestedCount;
- }
- _participantsQueries.erase(it);
- }
- };
- result.match([&](const MTPDchannels_channelParticipants &data) {
- Api::ChatParticipants::Parse(channel, data);
- addToCache();
- }, [&](const MTPDchannels_channelParticipantsNotModified &) {
- LOG(("API Error: "
- "channels.channelParticipantsNotModified received!"));
- });
- }
- if (_requestId != requestId) {
- return;
- }
- _requestId = 0;
- result.match([&](const MTPDchannels_channelParticipants &data) {
- const auto &list = data.vparticipants().v;
- if (list.size() < requestedCount) {
- // We want cache to have full information about a query with
- // small results count (that we don't need the second request).
- // So we don't wait for empty list unlike the non-search case.
- _participantsLoaded = true;
- if (list.empty() && _offset == 0) {
- // No results, request global search immediately.
- loadMoreRows();
- }
- }
- for (const auto &data : list) {
- if (const auto user = _additional->applyParticipant(
- Api::ChatParticipant(data, channel))) {
- delegate()->peerListSearchAddRow(user);
- }
- }
- _offset += list.size();
- }, [&](const MTPDchannels_channelParticipantsNotModified &) {
- _participantsLoaded = true;
- });
- delegate()->peerListSearchRefreshRows();
- }
- void AddSpecialBoxSearchController::requestGlobal() {
- if (_query.isEmpty()) {
- _globalLoaded = true;
- return;
- }
- auto perPage = SearchPeopleLimit;
- _requestId = _api.request(MTPcontacts_Search(
- MTP_string(_query),
- MTP_int(perPage)
- )).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) {
- searchGlobalDone(requestId, result);
- }).fail([=](const MTP::Error &error, mtpRequestId requestId) {
- if (_requestId == requestId) {
- _requestId = 0;
- _globalLoaded = true;
- delegate()->peerListSearchRefreshRows();
- }
- }).send();
- _globalQueries.emplace(_requestId, _query);
- }
- void AddSpecialBoxSearchController::searchGlobalDone(
- mtpRequestId requestId,
- const MTPcontacts_Found &result) {
- Expects(result.type() == mtpc_contacts_found);
- auto &found = result.c_contacts_found();
- auto query = _query;
- if (requestId) {
- _peer->owner().processUsers(found.vusers());
- _peer->owner().processChats(found.vchats());
- auto it = _globalQueries.find(requestId);
- if (it != _globalQueries.cend()) {
- query = it->second;
- _globalCache[query] = result;
- _globalQueries.erase(it);
- }
- }
- const auto feedList = [&](const MTPVector<MTPPeer> &list) {
- for (const auto &mtpPeer : list.v) {
- const auto peerId = peerFromMTP(mtpPeer);
- if (const auto peer = _peer->owner().peerLoaded(peerId)) {
- if (const auto user = peer->asUser()) {
- _additional->checkForLoaded(user);
- delegate()->peerListSearchAddRow(user);
- }
- }
- }
- };
- if (_requestId == requestId) {
- _requestId = 0;
- _globalLoaded = true;
- feedList(found.vmy_results());
- feedList(found.vresults());
- delegate()->peerListSearchRefreshRows();
- }
- }
- void AddSpecialBoxSearchController::addChatMembers(
- not_null<ChatData*> chat) {
- if (chat->participants.empty()) {
- return;
- }
- _chatMembersAdded = true;
- const auto wordList = TextUtilities::PrepareSearchWords(_query);
- if (wordList.empty()) {
- return;
- }
- const auto allWordsAreFound = [&](
- const base::flat_set<QString> &nameWords) {
- const auto hasNamePartStartingWith = [&](const QString &word) {
- for (const auto &nameWord : nameWords) {
- if (nameWord.startsWith(word)) {
- return true;
- }
- }
- return false;
- };
- for (const auto &word : wordList) {
- if (!hasNamePartStartingWith(word)) {
- return false;
- }
- }
- return true;
- };
- for (const auto &user : chat->participants) {
- if (allWordsAreFound(user->nameWords())) {
- delegate()->peerListSearchAddRow(user);
- }
- }
- delegate()->peerListSearchRefreshRows();
- }
- void AddSpecialBoxSearchController::addChatsContacts() {
- _chatsContactsAdded = true;
- const auto wordList = TextUtilities::PrepareSearchWords(_query);
- if (wordList.empty()) {
- return;
- }
- const auto allWordsAreFound = [&](
- const base::flat_set<QString> &nameWords) {
- const auto hasNamePartStartingWith = [&](const QString &word) {
- for (const auto &nameWord : nameWords) {
- if (nameWord.startsWith(word)) {
- return true;
- }
- }
- return false;
- };
- for (const auto &word : wordList) {
- if (!hasNamePartStartingWith(word)) {
- return false;
- }
- }
- return true;
- };
- const auto getSmallestIndex = [&](not_null<Dialogs::IndexedList*> list)
- -> const Dialogs::List* {
- if (list->empty()) {
- return nullptr;
- }
- auto result = (const Dialogs::List*)nullptr;
- for (const auto &word : wordList) {
- const auto found = list->filtered(word[0]);
- if (!found || found->empty()) {
- return nullptr;
- }
- if (!result || result->size() > found->size()) {
- result = found;
- }
- }
- return result;
- };
- const auto filterAndAppend = [&](not_null<Dialogs::IndexedList*> list) {
- const auto index = getSmallestIndex(list);
- if (!index) {
- return;
- }
- for (const auto &row : *index) {
- if (const auto history = row->history()) {
- if (const auto user = history->peer->asUser()) {
- if (allWordsAreFound(user->nameWords())) {
- delegate()->peerListSearchAddRow(user);
- }
- }
- }
- }
- };
- filterAndAppend(_peer->owner().chatsList()->indexed());
- const auto id = Data::Folder::kId;
- if (const auto folder = _peer->owner().folderLoaded(id)) {
- filterAndAppend(folder->chatsList()->indexed());
- }
- filterAndAppend(_peer->owner().contactsNoChatsList());
- delegate()->peerListSearchRefreshRows();
- }
|