add_participants_box.cpp 55 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "boxes/peers/add_participants_box.h"
  8. #include "api/api_chat_participants.h"
  9. #include "api/api_invite_links.h"
  10. #include "api/api_premium.h"
  11. #include "boxes/peers/edit_participant_box.h"
  12. #include "boxes/peers/edit_peer_type_box.h"
  13. #include "boxes/peers/replace_boost_box.h"
  14. #include "boxes/max_invite_box.h"
  15. #include "chat_helpers/message_field.h"
  16. #include "lang/lang_keys.h"
  17. #include "data/data_channel.h"
  18. #include "data/data_chat.h"
  19. #include "data/data_user.h"
  20. #include "data/data_session.h"
  21. #include "data/data_folder.h"
  22. #include "data/data_changes.h"
  23. #include "data/data_peer_values.h"
  24. #include "history/history.h"
  25. #include "history/history_item_helpers.h"
  26. #include "dialogs/dialogs_indexed_list.h"
  27. #include "ui/boxes/confirm_box.h"
  28. #include "ui/boxes/show_or_premium_box.h"
  29. #include "ui/effects/premium_graphics.h"
  30. #include "ui/text/text_utilities.h" // Ui::Text::RichLangValue
  31. #include "ui/toast/toast.h"
  32. #include "ui/widgets/buttons.h"
  33. #include "ui/widgets/checkbox.h"
  34. #include "ui/widgets/gradient_round_button.h"
  35. #include "ui/wrap/padding_wrap.h"
  36. #include "ui/painter.h"
  37. #include "base/unixtime.h"
  38. #include "main/main_session.h"
  39. #include "mtproto/mtproto_config.h"
  40. #include "settings/settings_premium.h"
  41. #include "window/window_session_controller.h"
  42. #include "info/profile/info_profile_icon.h"
  43. #include "apiwrap.h"
  44. #include "styles/style_boxes.h"
  45. #include "styles/style_layers.h"
  46. #include "styles/style_premium.h"
  47. namespace {
  48. constexpr auto kParticipantsFirstPageCount = 16;
  49. constexpr auto kParticipantsPerPage = 200;
  50. constexpr auto kUserpicsLimit = 3;
  51. class ForbiddenRow final : public PeerListRow {
  52. public:
  53. ForbiddenRow(
  54. not_null<PeerData*> peer,
  55. not_null<const style::PeerListItem*> lockSt,
  56. bool locked);
  57. PaintRoundImageCallback generatePaintUserpicCallback(
  58. bool forceRound) override;
  59. Api::MessageMoneyRestriction restriction() const;
  60. void setRestriction(Api::MessageMoneyRestriction restriction);
  61. void preloadUserpic() override;
  62. void paintUserpicOverlay(
  63. Painter &p,
  64. const style::PeerListItem &st,
  65. int x,
  66. int y,
  67. int outerWidth) override;
  68. bool refreshLock();
  69. private:
  70. struct Restriction {
  71. Api::MessageMoneyRestriction value;
  72. RestrictionBadgeCache cache;
  73. };
  74. const bool _locked = false;
  75. const not_null<const style::PeerListItem*> _lockSt;
  76. QImage _disabledFrame;
  77. InMemoryKey _userpicKey;
  78. int _paletteVersion = 0;
  79. std::shared_ptr<Restriction> _restriction;
  80. };
  81. class InviteForbiddenController final : public PeerListController {
  82. public:
  83. InviteForbiddenController(
  84. not_null<PeerData*> peer,
  85. ForbiddenInvites forbidden);
  86. Main::Session &session() const override;
  87. void prepare() override;
  88. void rowClicked(not_null<PeerListRow*> row) override;
  89. [[nodiscard]] bool canInvite() const {
  90. return _can;
  91. }
  92. [[nodiscard]] rpl::producer<int> selectedValue() const {
  93. return _selected.value();
  94. }
  95. [[nodiscard]] rpl::producer<int> starsToSend() const {
  96. return _starsToSend.value();
  97. }
  98. void send(
  99. std::vector<not_null<PeerData*>> list,
  100. Ui::ShowPtr show,
  101. Fn<void()> close);
  102. private:
  103. void appendRow(not_null<UserData*> user);
  104. [[nodiscard]] std::unique_ptr<ForbiddenRow> createRow(
  105. not_null<UserData*> user) const;
  106. [[nodiscard]] bool canInvite(not_null<PeerData*> peer) const;
  107. void send(
  108. std::vector<not_null<PeerData*>> list,
  109. Ui::ShowPtr show,
  110. Fn<void()> close,
  111. Api::SendOptions options);
  112. void setSimpleCover();
  113. void setComplexCover();
  114. const not_null<PeerData*> _peer;
  115. const ForbiddenInvites _forbidden;
  116. const std::vector<not_null<UserData*>> &_users;
  117. const bool _can = false;
  118. rpl::variable<int> _selected;
  119. rpl::variable<int> _starsToSend;
  120. bool _sending = false;
  121. rpl::lifetime _paymentCheckLifetime;
  122. };
  123. base::flat_set<not_null<UserData*>> GetAlreadyInFromPeer(PeerData *peer) {
  124. if (!peer) {
  125. return {};
  126. }
  127. if (const auto chat = peer->asChat()) {
  128. return chat->participants;
  129. } else if (const auto channel = peer->asChannel()) {
  130. if (channel->isMegagroup() && channel->canViewMembers()) {
  131. const auto &participants = channel->mgInfo->lastParticipants;
  132. return { participants.cbegin(), participants.cend() };
  133. }
  134. }
  135. return {};
  136. }
  137. void FillUpgradeToPremiumCover(
  138. not_null<Ui::VerticalLayout*> container,
  139. std::shared_ptr<Main::SessionShow> show,
  140. not_null<PeerData*> peer,
  141. const ForbiddenInvites &forbidden) {
  142. const auto noneCanSend = (forbidden.premiumAllowsWrite.size()
  143. == forbidden.users.size());
  144. const auto &userpicUsers = (forbidden.premiumAllowsInvite.empty()
  145. || noneCanSend)
  146. ? forbidden.premiumAllowsWrite
  147. : forbidden.premiumAllowsInvite;
  148. Assert(!userpicUsers.empty());
  149. auto userpicPeers = userpicUsers | ranges::views::transform([](auto u) {
  150. return not_null<PeerData*>(u);
  151. }) | ranges::to_vector;
  152. container->add(object_ptr<Ui::PaddingWrap<>>(
  153. container,
  154. CreateUserpicsWithMoreBadge(
  155. container,
  156. rpl::single(std::move(userpicPeers)),
  157. kUserpicsLimit),
  158. st::inviteForbiddenUserpicsPadding)
  159. )->entity()->setAttribute(Qt::WA_TransparentForMouseEvents);
  160. const auto users = int(userpicUsers.size());
  161. const auto names = std::min(users, kUserpicsLimit);
  162. const auto remaining = std::max(users - kUserpicsLimit, 0);
  163. auto text = TextWithEntities();
  164. for (auto i = 0; i != names; ++i) {
  165. const auto name = userpicUsers[i]->shortName();
  166. if (text.empty()) {
  167. text = Ui::Text::Bold(name);
  168. } else if (i == names - 1 && !remaining) {
  169. text = tr::lng_invite_upgrade_users_few(
  170. tr::now,
  171. lt_users,
  172. text,
  173. lt_last,
  174. Ui::Text::Bold(name),
  175. Ui::Text::RichLangValue);
  176. } else {
  177. text.append(", ").append(Ui::Text::Bold(name));
  178. }
  179. }
  180. if (remaining > 0) {
  181. text = tr::lng_invite_upgrade_users_many(
  182. tr::now,
  183. lt_count,
  184. remaining,
  185. lt_users,
  186. text,
  187. Ui::Text::RichLangValue);
  188. }
  189. const auto inviteOnly = !forbidden.premiumAllowsInvite.empty()
  190. && (forbidden.premiumAllowsWrite.size() != forbidden.users.size());
  191. text = (peer->isBroadcast()
  192. ? (inviteOnly
  193. ? tr::lng_invite_upgrade_channel_invite
  194. : tr::lng_invite_upgrade_channel_write)
  195. : (inviteOnly
  196. ? tr::lng_invite_upgrade_group_invite
  197. : tr::lng_invite_upgrade_group_write))(
  198. tr::now,
  199. lt_count,
  200. int(userpicUsers.size()),
  201. lt_users,
  202. text,
  203. Ui::Text::RichLangValue);
  204. container->add(
  205. object_ptr<Ui::FlatLabel>(
  206. container,
  207. rpl::single(text),
  208. st::inviteForbiddenInfo),
  209. st::inviteForbiddenInfoPadding);
  210. }
  211. void SimpleForbiddenBox(
  212. not_null<Ui::GenericBox*> box,
  213. not_null<PeerData*> peer,
  214. const ForbiddenInvites &forbidden) {
  215. box->setTitle(tr::lng_invite_upgrade_title());
  216. box->setWidth(st::boxWideWidth);
  217. box->addTopButton(st::boxTitleClose, [=] {
  218. box->closeBox();
  219. });
  220. auto sshow = Main::MakeSessionShow(box->uiShow(), &peer->session());
  221. const auto container = box->verticalLayout();
  222. FillUpgradeToPremiumCover(container, sshow, peer, forbidden);
  223. const auto &stButton = st::premiumGiftBox;
  224. box->setStyle(stButton);
  225. auto raw = Settings::CreateSubscribeButton(
  226. sshow,
  227. ChatHelpers::ResolveWindowDefault(),
  228. {
  229. .parent = container,
  230. .computeRef = [] { return u"invite_privacy"_q; },
  231. .text = tr::lng_messages_privacy_premium_button(),
  232. .showPromo = true,
  233. });
  234. auto button = object_ptr<Ui::GradientButton>::fromRaw(raw);
  235. button->resizeToWidth(st::boxWideWidth
  236. - stButton.buttonPadding.left()
  237. - stButton.buttonPadding.right());
  238. box->setShowFinishedCallback([raw = button.data()] {
  239. raw->startGlareAnimation();
  240. });
  241. box->addButton(std::move(button));
  242. Data::AmPremiumValue(
  243. &peer->session()
  244. ) | rpl::skip(1) | rpl::start_with_next([=] {
  245. box->closeBox();
  246. }, box->lifetime());
  247. }
  248. InviteForbiddenController::InviteForbiddenController(
  249. not_null<PeerData*> peer,
  250. ForbiddenInvites forbidden)
  251. : _peer(peer)
  252. , _forbidden(std::move(forbidden))
  253. , _users(_forbidden.users)
  254. , _can(peer->isChat()
  255. ? peer->asChat()->canHaveInviteLink()
  256. : peer->asChannel()->canHaveInviteLink())
  257. , _selected(_can
  258. ? (int(_users.size()) - int(_forbidden.premiumAllowsWrite.size()))
  259. : 0) {
  260. }
  261. Main::Session &InviteForbiddenController::session() const {
  262. return _peer->session();
  263. }
  264. ForbiddenRow::ForbiddenRow(
  265. not_null<PeerData*> peer,
  266. not_null<const style::PeerListItem*> lockSt,
  267. bool locked)
  268. : PeerListRow(peer)
  269. , _locked(locked)
  270. , _lockSt(lockSt) {
  271. if (_locked) {
  272. setCustomStatus(tr::lng_invite_status_disabled(tr::now));
  273. } else {
  274. setRestriction(Api::ResolveMessageMoneyRestrictions(peer, nullptr));
  275. }
  276. }
  277. PaintRoundImageCallback ForbiddenRow::generatePaintUserpicCallback(
  278. bool forceRound) {
  279. const auto peer = this->peer();
  280. const auto saved = peer->isSelf();
  281. const auto replies = peer->isRepliesChat();
  282. const auto verifyCodes = peer->isVerifyCodes();
  283. auto userpic = (saved || replies || verifyCodes)
  284. ? Ui::PeerUserpicView()
  285. : ensureUserpicView();
  286. auto paint = [=](
  287. Painter &p,
  288. int x,
  289. int y,
  290. int outerWidth,
  291. int size) mutable {
  292. peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
  293. };
  294. if (!_locked) {
  295. return paint;
  296. }
  297. return [=](
  298. Painter &p,
  299. int x,
  300. int y,
  301. int outerWidth,
  302. int size) mutable {
  303. const auto wide = size + style::ConvertScale(3);
  304. const auto full = QSize(wide, wide) * style::DevicePixelRatio();
  305. auto repaint = false;
  306. if (_disabledFrame.size() != full) {
  307. repaint = true;
  308. _disabledFrame = QImage(
  309. full,
  310. QImage::Format_ARGB32_Premultiplied);
  311. _disabledFrame.setDevicePixelRatio(style::DevicePixelRatio());
  312. } else {
  313. repaint = (_paletteVersion != style::PaletteVersion())
  314. || (!saved
  315. && !replies
  316. && !verifyCodes
  317. && (_userpicKey != peer->userpicUniqueKey(userpic)));
  318. }
  319. if (repaint) {
  320. _paletteVersion = style::PaletteVersion();
  321. _userpicKey = peer->userpicUniqueKey(userpic);
  322. _disabledFrame.fill(Qt::transparent);
  323. auto p = Painter(&_disabledFrame);
  324. paint(p, 0, 0, wide, size);
  325. auto hq = PainterHighQualityEnabler(p);
  326. p.setBrush(st::boxBg);
  327. p.setPen(Qt::NoPen);
  328. const auto lock = st::inviteForbiddenLockIcon.size();
  329. const auto stroke = style::ConvertScale(2);
  330. const auto inner = QRect(
  331. size + (stroke / 2) - lock.width(),
  332. size + (stroke / 2) - lock.height(),
  333. lock.width(),
  334. lock.height());
  335. const auto half = stroke / 2.;
  336. const auto rect = QRectF(inner).marginsAdded(
  337. { half, half, half, half });
  338. auto pen = st::boxBg->p;
  339. pen.setWidthF(stroke);
  340. p.setPen(pen);
  341. p.setBrush(st::inviteForbiddenLockBg);
  342. p.drawEllipse(rect);
  343. st::inviteForbiddenLockIcon.paintInCenter(p, inner);
  344. }
  345. p.drawImage(x, y, _disabledFrame);
  346. };
  347. }
  348. Api::MessageMoneyRestriction ForbiddenRow::restriction() const {
  349. return _restriction
  350. ? _restriction->value
  351. : Api::MessageMoneyRestriction();
  352. }
  353. void ForbiddenRow::setRestriction(Api::MessageMoneyRestriction restriction) {
  354. if (!restriction || !restriction.starsPerMessage) {
  355. _restriction = nullptr;
  356. return;
  357. } else if (!_restriction) {
  358. _restriction = std::make_unique<Restriction>();
  359. }
  360. _restriction->value = restriction;
  361. }
  362. void ForbiddenRow::paintUserpicOverlay(
  363. Painter &p,
  364. const style::PeerListItem &st,
  365. int x,
  366. int y,
  367. int outerWidth) {
  368. if (const auto &r = _restriction) {
  369. PaintRestrictionBadge(
  370. p,
  371. _lockSt,
  372. r->value.starsPerMessage,
  373. r->cache,
  374. x,
  375. y,
  376. outerWidth,
  377. st.photoSize);
  378. }
  379. }
  380. bool ForbiddenRow::refreshLock() {
  381. if (_locked) {
  382. return false;
  383. } else if (const auto user = peer()->asUser()) {
  384. using Restriction = Api::MessageMoneyRestriction;
  385. auto r = Api::ResolveMessageMoneyRestrictions(user, nullptr);
  386. if (!r || !r.starsPerMessage) {
  387. r = Restriction();
  388. }
  389. if ((_restriction ? _restriction->value : Restriction()) != r) {
  390. setRestriction(r);
  391. return true;
  392. }
  393. }
  394. return false;
  395. }
  396. void ForbiddenRow::preloadUserpic() {
  397. PeerListRow::preloadUserpic();
  398. const auto peer = this->peer();
  399. const auto known = Api::ResolveMessageMoneyRestrictions(
  400. peer,
  401. nullptr).known;
  402. if (known) {
  403. return;
  404. } else if (const auto user = peer->asUser()) {
  405. const auto api = &user->session().api();
  406. api->premium().resolveMessageMoneyRestrictions(user);
  407. } else if (const auto group = peer->asChannel()) {
  408. group->updateFull();
  409. }
  410. }
  411. void InviteForbiddenController::setSimpleCover() {
  412. delegate()->peerListSetTitle(
  413. _can ? tr::lng_profile_add_via_link() : tr::lng_via_link_cant());
  414. const auto broadcast = _peer->isBroadcast();
  415. const auto count = int(_users.size());
  416. const auto phraseCounted = !_can
  417. ? tr::lng_via_link_cant_many
  418. : broadcast
  419. ? tr::lng_via_link_channel_many
  420. : tr::lng_via_link_group_many;
  421. const auto phraseNamed = !_can
  422. ? tr::lng_via_link_cant_one
  423. : broadcast
  424. ? tr::lng_via_link_channel_one
  425. : tr::lng_via_link_group_one;
  426. auto text = (count != 1)
  427. ? phraseCounted(
  428. lt_count,
  429. rpl::single<float64>(count),
  430. Ui::Text::RichLangValue)
  431. : phraseNamed(
  432. lt_user,
  433. rpl::single(TextWithEntities{ _users.front()->name() }),
  434. Ui::Text::RichLangValue);
  435. delegate()->peerListSetAboveWidget(object_ptr<Ui::PaddingWrap<>>(
  436. (QWidget*)nullptr,
  437. object_ptr<Ui::FlatLabel>(
  438. (QWidget*)nullptr,
  439. std::move(text),
  440. st::requestPeerRestriction),
  441. st::boxRowPadding));
  442. }
  443. void InviteForbiddenController::setComplexCover() {
  444. delegate()->peerListSetTitle(tr::lng_invite_upgrade_title());
  445. auto cover = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
  446. const auto container = cover.data();
  447. const auto show = delegate()->peerListUiShow();
  448. FillUpgradeToPremiumCover(container, show, _peer, _forbidden);
  449. container->add(
  450. object_ptr<Ui::GradientButton>::fromRaw(
  451. Settings::CreateSubscribeButton(
  452. show,
  453. ChatHelpers::ResolveWindowDefault(),
  454. {
  455. .parent = container,
  456. .computeRef = [] { return u"invite_privacy"_q; },
  457. .text = tr::lng_messages_privacy_premium_button(),
  458. })),
  459. st::inviteForbiddenSubscribePadding);
  460. if (_forbidden.users.size() > _forbidden.premiumAllowsWrite.size()) {
  461. if (_can) {
  462. container->add(
  463. MakeShowOrLabel(container, tr::lng_invite_upgrade_or()),
  464. st::inviteForbiddenOrLabelPadding);
  465. }
  466. container->add(
  467. object_ptr<Ui::FlatLabel>(
  468. container,
  469. (_can
  470. ? tr::lng_invite_upgrade_via_title()
  471. : tr::lng_via_link_cant()),
  472. st::inviteForbiddenTitle),
  473. st::inviteForbiddenTitlePadding);
  474. const auto about = _can
  475. ? (_peer->isBroadcast()
  476. ? tr::lng_invite_upgrade_via_channel_about
  477. : tr::lng_invite_upgrade_via_group_about)(
  478. tr::now,
  479. Ui::Text::WithEntities)
  480. : (_forbidden.users.size() == 1
  481. ? tr::lng_via_link_cant_one(
  482. tr::now,
  483. lt_user,
  484. TextWithEntities{ _forbidden.users.front()->shortName() },
  485. Ui::Text::RichLangValue)
  486. : tr::lng_via_link_cant_many(
  487. tr::now,
  488. lt_count,
  489. int(_forbidden.users.size()),
  490. Ui::Text::RichLangValue));
  491. container->add(
  492. object_ptr<Ui::FlatLabel>(
  493. container,
  494. rpl::single(about),
  495. st::inviteForbiddenInfo),
  496. st::inviteForbiddenInfoPadding);
  497. }
  498. delegate()->peerListSetAboveWidget(std::move(cover));
  499. }
  500. void InviteForbiddenController::prepare() {
  501. session().api().premium().someMessageMoneyRestrictionsResolved(
  502. ) | rpl::start_with_next([=] {
  503. auto stars = 0;
  504. const auto process = [&](not_null<PeerListRow*> raw) {
  505. const auto row = static_cast<ForbiddenRow*>(raw.get());
  506. if (row->refreshLock()) {
  507. delegate()->peerListUpdateRow(raw);
  508. }
  509. if (const auto r = row->restriction()) {
  510. stars += r.starsPerMessage;
  511. }
  512. };
  513. auto count = delegate()->peerListFullRowsCount();
  514. for (auto i = 0; i != count; ++i) {
  515. process(delegate()->peerListRowAt(i));
  516. }
  517. _starsToSend = stars;
  518. count = delegate()->peerListSearchRowsCount();
  519. for (auto i = 0; i != count; ++i) {
  520. process(delegate()->peerListSearchRowAt(i));
  521. }
  522. }, lifetime());
  523. if (session().premium()
  524. || (_forbidden.premiumAllowsInvite.empty()
  525. && _forbidden.premiumAllowsWrite.empty())) {
  526. setSimpleCover();
  527. } else {
  528. setComplexCover();
  529. }
  530. for (const auto &user : _users) {
  531. appendRow(user);
  532. }
  533. delegate()->peerListRefreshRows();
  534. }
  535. bool InviteForbiddenController::canInvite(not_null<PeerData*> peer) const {
  536. const auto user = peer->asUser();
  537. Assert(user != nullptr);
  538. return _can
  539. && !ranges::contains(_forbidden.premiumAllowsWrite, not_null(user));
  540. }
  541. void InviteForbiddenController::rowClicked(not_null<PeerListRow*> row) {
  542. if (!canInvite(row->peer())) {
  543. return;
  544. }
  545. const auto checked = row->checked();
  546. delegate()->peerListSetRowChecked(row, !checked);
  547. _selected = _selected.current() + (checked ? -1 : 1);
  548. const auto r = static_cast<ForbiddenRow*>(row.get())->restriction();
  549. if (r.starsPerMessage) {
  550. _starsToSend = _starsToSend.current()
  551. + (checked ? -r.starsPerMessage : r.starsPerMessage);
  552. }
  553. }
  554. void InviteForbiddenController::appendRow(not_null<UserData*> user) {
  555. if (!delegate()->peerListFindRow(user->id.value)) {
  556. auto row = createRow(user);
  557. const auto raw = row.get();
  558. delegate()->peerListAppendRow(std::move(row));
  559. if (canInvite(user)) {
  560. delegate()->peerListSetRowChecked(raw, true);
  561. if (const auto r = raw->restriction()) {
  562. _starsToSend = _starsToSend.current() + r.starsPerMessage;
  563. }
  564. }
  565. }
  566. }
  567. void InviteForbiddenController::send(
  568. std::vector<not_null<PeerData*>> list,
  569. Ui::ShowPtr show,
  570. Fn<void()> close) {
  571. send(list, show, close, {});
  572. }
  573. void InviteForbiddenController::send(
  574. std::vector<not_null<PeerData*>> list,
  575. Ui::ShowPtr show,
  576. Fn<void()> close,
  577. Api::SendOptions options) {
  578. if (list.empty()) {
  579. return;
  580. }
  581. _paymentCheckLifetime.destroy();
  582. const auto withPaymentApproved = [=](int approved) {
  583. auto copy = options;
  584. copy.starsApproved = approved;
  585. send(list, show, close, copy);
  586. };
  587. const auto messagesCount = 1;
  588. const auto alreadyApproved = options.starsApproved;
  589. auto paid = std::vector<not_null<PeerData*>>();
  590. auto waiting = base::flat_set<not_null<PeerData*>>();
  591. auto totalStars = 0;
  592. for (const auto &peer : list) {
  593. const auto details = ComputePaymentDetails(peer, messagesCount);
  594. if (!details) {
  595. waiting.emplace(peer);
  596. } else if (details->stars > 0) {
  597. totalStars += details->stars;
  598. paid.push_back(peer);
  599. }
  600. }
  601. if (!waiting.empty()) {
  602. session().changes().peerUpdates(
  603. Data::PeerUpdate::Flag::FullInfo
  604. ) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
  605. if (waiting.contains(update.peer)) {
  606. withPaymentApproved(alreadyApproved);
  607. }
  608. }, _paymentCheckLifetime);
  609. if (!session().credits().loaded()) {
  610. session().credits().loadedValue(
  611. ) | rpl::filter(
  612. rpl::mappers::_1
  613. ) | rpl::take(1) | rpl::start_with_next([=] {
  614. withPaymentApproved(alreadyApproved);
  615. }, _paymentCheckLifetime);
  616. }
  617. return;
  618. } else if (totalStars > alreadyApproved) {
  619. const auto sessionShow = Main::MakeSessionShow(show, &session());
  620. ShowSendPaidConfirm(sessionShow, paid, SendPaymentDetails{
  621. .messages = messagesCount,
  622. .stars = totalStars,
  623. }, [=] { withPaymentApproved(totalStars); });
  624. return;
  625. } else if (_sending) {
  626. return;
  627. }
  628. _sending = true;
  629. const auto chat = _peer->asChat();
  630. const auto channel = _peer->asChannel();
  631. const auto sendLink = [=] {
  632. const auto link = chat ? chat->inviteLink() : channel->inviteLink();
  633. if (link.isEmpty()) {
  634. return false;
  635. }
  636. auto full = options;
  637. auto &api = _peer->session().api();
  638. for (const auto &to : list) {
  639. auto copy = full;
  640. copy.starsApproved = std::min(
  641. to->starsPerMessageChecked(),
  642. full.starsApproved);
  643. full.starsApproved -= copy.starsApproved;
  644. const auto history = to->owner().history(to);
  645. auto message = Api::MessageToSend(
  646. Api::SendAction(history, copy));
  647. message.textWithTags = { link };
  648. message.action.clearDraft = false;
  649. api.sendMessage(std::move(message));
  650. }
  651. auto text = (list.size() == 1)
  652. ? tr::lng_via_link_shared_one(
  653. tr::now,
  654. lt_user,
  655. TextWithEntities{ list.front()->name() },
  656. Ui::Text::RichLangValue)
  657. : tr::lng_via_link_shared_many(
  658. tr::now,
  659. lt_count,
  660. int(list.size()),
  661. Ui::Text::RichLangValue);
  662. close();
  663. show->showToast(std::move(text));
  664. return true;
  665. };
  666. const auto sendForFull = [=] {
  667. if (!sendLink()) {
  668. _peer->session().api().inviteLinks().create({
  669. _peer,
  670. [=](auto) {
  671. if (!sendLink()) {
  672. close();
  673. }
  674. },
  675. });
  676. }
  677. };
  678. if (_peer->isFullLoaded()) {
  679. sendForFull();
  680. } else if (!sendLink()) {
  681. _peer->session().api().requestFullPeer(_peer);
  682. _peer->session().changes().peerUpdates(
  683. _peer,
  684. Data::PeerUpdate::Flag::FullInfo
  685. ) | rpl::start_with_next([=] {
  686. sendForFull();
  687. }, lifetime());
  688. }
  689. }
  690. std::unique_ptr<ForbiddenRow> InviteForbiddenController::createRow(
  691. not_null<UserData*> user) const {
  692. const auto locked = _can && !canInvite(user);
  693. const auto lockSt = &computeListSt().item;
  694. return std::make_unique<ForbiddenRow>(user, lockSt, locked);
  695. }
  696. } // namespace
  697. AddParticipantsBoxController::AddParticipantsBoxController(
  698. not_null<Main::Session*> session)
  699. : ContactsBoxController(session) {
  700. }
  701. AddParticipantsBoxController::AddParticipantsBoxController(
  702. not_null<PeerData*> peer)
  703. : AddParticipantsBoxController(
  704. peer,
  705. GetAlreadyInFromPeer(peer)) {
  706. }
  707. AddParticipantsBoxController::AddParticipantsBoxController(
  708. not_null<PeerData*> peer,
  709. base::flat_set<not_null<UserData*>> &&alreadyIn)
  710. : ContactsBoxController(&peer->session())
  711. , _peer(peer)
  712. , _alreadyIn(std::move(alreadyIn)) {
  713. if (needsInviteLinkButton()) {
  714. setStyleOverrides(&st::peerListWithInviteViaLink);
  715. }
  716. subscribeToMigration();
  717. }
  718. void AddParticipantsBoxController::subscribeToMigration() {
  719. Expects(_peer != nullptr);
  720. SubscribeToMigration(
  721. _peer,
  722. lifetime(),
  723. [=](not_null<ChannelData*> channel) { _peer = channel; });
  724. }
  725. void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
  726. const auto moneyRestrictionError = WriteMoneyRestrictionError;
  727. if (RecipientRow::ShowLockedError(this, row, moneyRestrictionError)) {
  728. return;
  729. }
  730. const auto &serverConfig = session().serverConfig();
  731. auto count = fullCount();
  732. auto limit = _peer && (_peer->isChat() || _peer->isMegagroup())
  733. ? serverConfig.megagroupSizeMax
  734. : serverConfig.chatSizeMax;
  735. if (count < limit || row->checked()) {
  736. delegate()->peerListSetRowChecked(row, !row->checked());
  737. updateTitle();
  738. } else if (const auto channel = _peer ? _peer->asChannel() : nullptr) {
  739. if (!_peer->isMegagroup()) {
  740. showBox(Box<MaxInviteBox>(_peer->asChannel()));
  741. }
  742. } else if (count >= serverConfig.chatSizeMax
  743. && count < serverConfig.megagroupSizeMax) {
  744. showBox(Ui::MakeInformBox(tr::lng_profile_add_more_after_create()));
  745. }
  746. }
  747. void AddParticipantsBoxController::itemDeselectedHook(
  748. not_null<PeerData*> peer) {
  749. updateTitle();
  750. }
  751. void AddParticipantsBoxController::prepareViewHook() {
  752. updateTitle();
  753. TrackMessageMoneyRestrictionsChanges(this, lifetime());
  754. }
  755. int AddParticipantsBoxController::alreadyInCount() const {
  756. if (!_peer) {
  757. return 1; // self
  758. }
  759. if (const auto chat = _peer->asChat()) {
  760. return qMax(chat->count, 1);
  761. } else if (const auto channel = _peer->asChannel()) {
  762. return qMax(channel->membersCount(), int(_alreadyIn.size()));
  763. }
  764. Unexpected("User in AddParticipantsBoxController::alreadyInCount");
  765. }
  766. bool AddParticipantsBoxController::isAlreadyIn(
  767. not_null<UserData*> user) const {
  768. if (!_peer) {
  769. return false;
  770. }
  771. if (const auto chat = _peer->asChat()) {
  772. return _alreadyIn.contains(user)
  773. || chat->participants.contains(user);
  774. } else if (const auto channel = _peer->asChannel()) {
  775. return _alreadyIn.contains(user)
  776. || (channel->isMegagroup()
  777. && channel->canViewMembers()
  778. && base::contains(channel->mgInfo->lastParticipants, user));
  779. }
  780. Unexpected("User in AddParticipantsBoxController::isAlreadyIn");
  781. }
  782. int AddParticipantsBoxController::fullCount() const {
  783. return alreadyInCount() + delegate()->peerListSelectedRowsCount();
  784. }
  785. std::unique_ptr<PeerListRow> AddParticipantsBoxController::createRow(
  786. not_null<UserData*> user) {
  787. if (user->isSelf()) {
  788. return nullptr;
  789. }
  790. const auto already = isAlreadyIn(user);
  791. const auto maybeLockedSt = already ? nullptr : &computeListSt().item;
  792. auto result = std::make_unique<RecipientRow>(user, maybeLockedSt);
  793. if (already) {
  794. result->setDisabledState(PeerListRow::State::DisabledChecked);
  795. }
  796. return result;
  797. }
  798. void AddParticipantsBoxController::updateTitle() {
  799. const auto additional = (_peer
  800. && _peer->isChannel()
  801. && !_peer->isMegagroup())
  802. ? QString()
  803. : (u"%1 / %2"_q
  804. ).arg(fullCount()
  805. ).arg(session().serverConfig().megagroupSizeMax);
  806. delegate()->peerListSetTitle(tr::lng_profile_add_participant());
  807. delegate()->peerListSetAdditionalTitle(rpl::single(additional));
  808. addInviteLinkButton();
  809. }
  810. bool AddParticipantsBoxController::needsInviteLinkButton() {
  811. if (!_peer) {
  812. return false;
  813. } else if (const auto channel = _peer->asChannel()) {
  814. return channel->canHaveInviteLink();
  815. }
  816. return _peer->asChat()->canHaveInviteLink();
  817. }
  818. QPointer<Ui::BoxContent> AddParticipantsBoxController::showBox(
  819. object_ptr<Ui::BoxContent> box) const {
  820. const auto weak = Ui::MakeWeak(box.data());
  821. delegate()->peerListUiShow()->showBox(std::move(box));
  822. return weak;
  823. }
  824. void AddParticipantsBoxController::addInviteLinkButton() {
  825. if (!needsInviteLinkButton()) {
  826. return;
  827. }
  828. auto button = object_ptr<Ui::PaddingWrap<Ui::SettingsButton>>(
  829. nullptr,
  830. object_ptr<Ui::SettingsButton>(
  831. nullptr,
  832. tr::lng_profile_add_via_link(),
  833. st::inviteViaLinkButton),
  834. style::margins(0, st::membersMarginTop, 0, 0));
  835. const auto icon = Ui::CreateChild<Info::Profile::FloatingIcon>(
  836. button->entity(),
  837. st::inviteViaLinkIcon,
  838. QPoint());
  839. button->entity()->heightValue(
  840. ) | rpl::start_with_next([=](int height) {
  841. icon->moveToLeft(
  842. st::inviteViaLinkIconPosition.x(),
  843. (height - st::inviteViaLinkIcon.height()) / 2);
  844. }, icon->lifetime());
  845. button->entity()->setClickedCallback([=] {
  846. showBox(Box<EditPeerTypeBox>(_peer));
  847. });
  848. button->entity()->events(
  849. ) | rpl::filter([=](not_null<QEvent*> e) {
  850. return (e->type() == QEvent::Enter);
  851. }) | rpl::start_with_next([=] {
  852. delegate()->peerListMouseLeftGeometry();
  853. }, button->lifetime());
  854. delegate()->peerListSetAboveWidget(std::move(button));
  855. delegate()->peerListRefreshRows();
  856. }
  857. void AddParticipantsBoxController::inviteSelectedUsers(
  858. not_null<PeerListBox*> box,
  859. Fn<void()> done) const {
  860. Expects(_peer != nullptr);
  861. const auto rows = box->collectSelectedRows();
  862. const auto users = ranges::views::all(
  863. rows
  864. ) | ranges::views::transform([](not_null<PeerData*> peer) {
  865. Expects(peer->isUser());
  866. Expects(!peer->isSelf());
  867. return not_null<UserData*>(peer->asUser());
  868. }) | ranges::to_vector;
  869. if (users.empty()) {
  870. return;
  871. }
  872. const auto show = box->uiShow();
  873. const auto request = [=](bool checked) {
  874. _peer->session().api().chatParticipants().add(
  875. show,
  876. _peer,
  877. users,
  878. checked);
  879. };
  880. if (_peer->isChannel()) {
  881. request(false);
  882. return done();
  883. }
  884. show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
  885. auto checkbox = object_ptr<Ui::Checkbox>(
  886. box.get(),
  887. tr::lng_participant_invite_history(),
  888. true,
  889. st::defaultBoxCheckbox);
  890. const auto weak = Ui::MakeWeak(checkbox.data());
  891. auto text = (users.size() == 1)
  892. ? tr::lng_participant_invite_sure(
  893. tr::now,
  894. lt_user,
  895. { users.front()->name()},
  896. lt_group,
  897. { _peer->name()},
  898. Ui::Text::RichLangValue)
  899. : tr::lng_participant_invite_sure_many(
  900. tr::now,
  901. lt_count,
  902. int(users.size()),
  903. lt_group,
  904. { _peer->name() },
  905. Ui::Text::RichLangValue);
  906. Ui::ConfirmBox(box, {
  907. .text = std::move(text),
  908. .confirmed = crl::guard(weak, [=](Fn<void()> &&close) {
  909. request(weak->checked());
  910. done();
  911. close();
  912. }),
  913. .confirmText = tr::lng_participant_invite(),
  914. });
  915. auto padding = st::boxPadding;
  916. padding.setTop(padding.bottom());
  917. box->addRow(std::move(checkbox), std::move(padding));
  918. }));
  919. }
  920. void AddParticipantsBoxController::Start(
  921. not_null<Window::SessionNavigation*> navigation,
  922. not_null<ChatData*> chat) {
  923. auto controller = std::make_unique<AddParticipantsBoxController>(chat);
  924. const auto weak = controller.get();
  925. const auto parent = navigation->parentController();
  926. auto initBox = [=](not_null<PeerListBox*> box) {
  927. box->addButton(tr::lng_participant_invite(), [=] {
  928. weak->inviteSelectedUsers(box, [=] {
  929. parent->showPeerHistory(
  930. chat,
  931. Window::SectionShow::Way::ClearStack,
  932. ShowAtTheEndMsgId);
  933. });
  934. });
  935. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  936. };
  937. parent->show(
  938. Box<PeerListBox>(std::move(controller), std::move(initBox)));
  939. }
  940. void AddParticipantsBoxController::Start(
  941. not_null<Window::SessionNavigation*> navigation,
  942. not_null<ChannelData*> channel,
  943. base::flat_set<not_null<UserData*>> &&alreadyIn,
  944. bool justCreated) {
  945. auto controller = std::make_unique<AddParticipantsBoxController>(
  946. channel,
  947. std::move(alreadyIn));
  948. const auto weak = controller.get();
  949. const auto parent = navigation->parentController();
  950. auto initBox = [=](not_null<PeerListBox*> box) {
  951. box->addButton(tr::lng_participant_invite(), [=] {
  952. weak->inviteSelectedUsers(box, [=] {
  953. if (channel->isMegagroup()) {
  954. parent->showPeerHistory(
  955. channel,
  956. Window::SectionShow::Way::ClearStack,
  957. ShowAtTheEndMsgId);
  958. } else {
  959. box->closeBox();
  960. }
  961. });
  962. });
  963. box->addButton(
  964. justCreated ? tr::lng_create_group_skip() : tr::lng_cancel(),
  965. [=] { box->closeBox(); });
  966. if (justCreated) {
  967. const auto weak = base::make_weak(parent);
  968. box->boxClosing() | rpl::start_with_next([=] {
  969. auto params = Window::SectionShow();
  970. params.activation = anim::activation::background;
  971. if (const auto strong = weak.get()) {
  972. strong->showPeerHistory(
  973. channel,
  974. params,
  975. ShowAtTheEndMsgId);
  976. }
  977. }, box->lifetime());
  978. }
  979. };
  980. parent->show(
  981. Box<PeerListBox>(std::move(controller), std::move(initBox)));
  982. }
  983. void AddParticipantsBoxController::Start(
  984. not_null<Window::SessionNavigation*> navigation,
  985. not_null<ChannelData*> channel,
  986. base::flat_set<not_null<UserData*>> &&alreadyIn) {
  987. Start(navigation, channel, std::move(alreadyIn), false);
  988. }
  989. void AddParticipantsBoxController::Start(
  990. not_null<Window::SessionNavigation*> navigation,
  991. not_null<ChannelData*> channel) {
  992. Start(navigation, channel, {}, true);
  993. }
  994. ForbiddenInvites CollectForbiddenUsers(
  995. not_null<Main::Session*> session,
  996. const MTPmessages_InvitedUsers &result) {
  997. const auto &data = result.data();
  998. const auto owner = &session->data();
  999. auto forbidden = ForbiddenInvites();
  1000. for (const auto &missing : data.vmissing_invitees().v) {
  1001. const auto &data = missing.data();
  1002. const auto user = owner->userLoaded(data.vuser_id());
  1003. if (user) {
  1004. forbidden.users.push_back(user);
  1005. if (data.is_premium_would_allow_invite()) {
  1006. forbidden.premiumAllowsInvite.push_back(user);
  1007. }
  1008. if (data.is_premium_required_for_pm()) {
  1009. forbidden.premiumAllowsWrite.push_back(user);
  1010. }
  1011. }
  1012. }
  1013. return forbidden;
  1014. }
  1015. bool ChatInviteForbidden(
  1016. std::shared_ptr<Ui::Show> show,
  1017. not_null<PeerData*> peer,
  1018. ForbiddenInvites forbidden) {
  1019. if (forbidden.empty() || !show || !show->valid()) {
  1020. return false;
  1021. } else if (forbidden.users.size() <= kUserpicsLimit
  1022. && (forbidden.premiumAllowsWrite.size()
  1023. == forbidden.users.size())) {
  1024. show->show(Box(SimpleForbiddenBox, peer, forbidden));
  1025. return true;
  1026. }
  1027. auto controller = std::make_unique<InviteForbiddenController>(
  1028. peer,
  1029. std::move(forbidden));
  1030. const auto weak = controller.get();
  1031. auto initBox = [=](not_null<PeerListBox*> box) {
  1032. const auto can = weak->canInvite();
  1033. if (!can) {
  1034. box->addButton(tr::lng_close(), [=] {
  1035. box->closeBox();
  1036. });
  1037. return;
  1038. }
  1039. weak->selectedValue(
  1040. ) | rpl::map(
  1041. rpl::mappers::_1 > 0
  1042. ) | rpl::distinct_until_changed(
  1043. ) | rpl::start_with_next([=](bool has) {
  1044. box->clearButtons();
  1045. if (has) {
  1046. const auto send = box->addButton(tr::lng_via_link_send(), [=] {
  1047. weak->send(
  1048. box->collectSelectedRows(),
  1049. box->uiShow(),
  1050. crl::guard(box, [=] { box->closeBox(); }));
  1051. });
  1052. send->setText(PaidSendButtonText(
  1053. weak->starsToSend(),
  1054. tr::lng_via_link_send()));
  1055. }
  1056. box->addButton(tr::lng_create_group_skip(), [=] {
  1057. box->closeBox();
  1058. });
  1059. }, box->lifetime());
  1060. Data::AmPremiumValue(
  1061. &peer->session()
  1062. ) | rpl::skip(1) | rpl::start_with_next([=] {
  1063. box->closeBox();
  1064. }, box->lifetime());
  1065. };
  1066. show->showBox(
  1067. Box<PeerListBox>(std::move(controller), std::move(initBox)));
  1068. return true;
  1069. }
  1070. AddSpecialBoxController::AddSpecialBoxController(
  1071. not_null<PeerData*> peer,
  1072. Role role,
  1073. AdminDoneCallback adminDoneCallback,
  1074. BannedDoneCallback bannedDoneCallback)
  1075. : PeerListController(std::make_unique<AddSpecialBoxSearchController>(
  1076. peer,
  1077. &_additional))
  1078. , _peer(peer)
  1079. , _api(&_peer->session().mtp())
  1080. , _role(role)
  1081. , _additional(peer, Role::Members)
  1082. , _adminDoneCallback(std::move(adminDoneCallback))
  1083. , _bannedDoneCallback(std::move(bannedDoneCallback)) {
  1084. subscribeToMigration();
  1085. }
  1086. Main::Session &AddSpecialBoxController::session() const {
  1087. return _peer->session();
  1088. }
  1089. void AddSpecialBoxController::subscribeToMigration() {
  1090. const auto chat = _peer->asChat();
  1091. if (!chat) {
  1092. return;
  1093. }
  1094. SubscribeToMigration(
  1095. chat,
  1096. lifetime(),
  1097. [=](not_null<ChannelData*> channel) { migrate(chat, channel); });
  1098. }
  1099. void AddSpecialBoxController::migrate(
  1100. not_null<ChatData*> chat,
  1101. not_null<ChannelData*> channel) {
  1102. _peer = channel;
  1103. _additional.migrate(chat, channel);
  1104. }
  1105. QPointer<Ui::BoxContent> AddSpecialBoxController::showBox(
  1106. object_ptr<Ui::BoxContent> box) const {
  1107. const auto weak = Ui::MakeWeak(box.data());
  1108. delegate()->peerListUiShow()->showBox(std::move(box));
  1109. return weak;
  1110. }
  1111. std::unique_ptr<PeerListRow> AddSpecialBoxController::createSearchRow(
  1112. not_null<PeerData*> peer) {
  1113. if (_excludeSelf && peer->isSelf()) {
  1114. return nullptr;
  1115. }
  1116. if (const auto user = peer->asUser()) {
  1117. return createRow(user);
  1118. }
  1119. return nullptr;
  1120. }
  1121. void AddSpecialBoxController::prepare() {
  1122. delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
  1123. auto title = [&] {
  1124. switch (_role) {
  1125. case Role::Members:
  1126. return tr::lng_profile_participants_section();
  1127. case Role::Admins:
  1128. return tr::lng_channel_add_admin();
  1129. case Role::Restricted:
  1130. return tr::lng_channel_add_exception();
  1131. case Role::Kicked:
  1132. return tr::lng_channel_add_removed();
  1133. }
  1134. Unexpected("Role in AddSpecialBoxController::prepare()");
  1135. }();
  1136. delegate()->peerListSetTitle(std::move(title));
  1137. setDescriptionText(tr::lng_contacts_loading(tr::now));
  1138. setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now));
  1139. if (const auto chat = _peer->asChat()) {
  1140. prepareChatRows(chat);
  1141. } else {
  1142. loadMoreRows();
  1143. }
  1144. delegate()->peerListRefreshRows();
  1145. }
  1146. void AddSpecialBoxController::prepareChatRows(not_null<ChatData*> chat) {
  1147. _onlineSorter = std::make_unique<ParticipantsOnlineSorter>(
  1148. chat,
  1149. delegate());
  1150. rebuildChatRows(chat);
  1151. if (!delegate()->peerListFullRowsCount()) {
  1152. chat->updateFullForced();
  1153. }
  1154. using UpdateFlag = Data::PeerUpdate::Flag;
  1155. chat->session().changes().peerUpdates(
  1156. chat,
  1157. UpdateFlag::Members | UpdateFlag::Admins
  1158. ) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
  1159. _additional.fillFromPeer();
  1160. if (update.flags & UpdateFlag::Members) {
  1161. rebuildChatRows(chat);
  1162. }
  1163. }, lifetime());
  1164. }
  1165. void AddSpecialBoxController::rebuildChatRows(not_null<ChatData*> chat) {
  1166. if (chat->participants.empty()) {
  1167. // We get such updates often
  1168. // (when participants list was invalidated).
  1169. //while (delegate()->peerListFullRowsCount() > 0) {
  1170. // delegate()->peerListRemoveRow(
  1171. // delegate()->peerListRowAt(0));
  1172. //}
  1173. return;
  1174. }
  1175. auto &participants = chat->participants;
  1176. auto count = delegate()->peerListFullRowsCount();
  1177. for (auto i = 0; i != count;) {
  1178. auto row = delegate()->peerListRowAt(i);
  1179. Assert(row->peer()->isUser());
  1180. auto user = row->peer()->asUser();
  1181. if (participants.contains(user)) {
  1182. ++i;
  1183. } else {
  1184. delegate()->peerListRemoveRow(row);
  1185. --count;
  1186. }
  1187. }
  1188. for (const auto &user : participants) {
  1189. if (auto row = createRow(user)) {
  1190. delegate()->peerListAppendRow(std::move(row));
  1191. }
  1192. }
  1193. _onlineSorter->sort();
  1194. delegate()->peerListRefreshRows();
  1195. setDescriptionText(QString());
  1196. }
  1197. void AddSpecialBoxController::loadMoreRows() {
  1198. if (searchController() && searchController()->loadMoreRows()) {
  1199. return;
  1200. } else if (!_peer->isChannel() || _loadRequestId || _allLoaded) {
  1201. return;
  1202. }
  1203. // First query is small and fast, next loads a lot of rows.
  1204. const auto perPage = (_offset > 0)
  1205. ? kParticipantsPerPage
  1206. : kParticipantsFirstPageCount;
  1207. const auto participantsHash = uint64(0);
  1208. const auto channel = _peer->asChannel();
  1209. _loadRequestId = _api.request(MTPchannels_GetParticipants(
  1210. channel->inputChannel,
  1211. MTP_channelParticipantsRecent(),
  1212. MTP_int(_offset),
  1213. MTP_int(perPage),
  1214. MTP_long(participantsHash)
  1215. )).done([=](const MTPchannels_ChannelParticipants &result) {
  1216. _loadRequestId = 0;
  1217. result.match([&](const MTPDchannels_channelParticipants &data) {
  1218. const auto &[availableCount, list] = Api::ChatParticipants::Parse(
  1219. channel,
  1220. data);
  1221. for (const auto &data : list) {
  1222. if (const auto participant = _additional.applyParticipant(
  1223. data)) {
  1224. appendRow(participant);
  1225. }
  1226. }
  1227. if (const auto size = list.size()) {
  1228. _offset += size;
  1229. } else {
  1230. // To be sure - wait for a whole empty result list.
  1231. _allLoaded = true;
  1232. }
  1233. }, [&](const MTPDchannels_channelParticipantsNotModified &) {
  1234. LOG(("API Error: channels.channelParticipantsNotModified received!"));
  1235. });
  1236. if (delegate()->peerListFullRowsCount() > 0) {
  1237. setDescriptionText(QString());
  1238. } else if (_allLoaded) {
  1239. setDescriptionText(tr::lng_blocked_list_not_found(tr::now));
  1240. }
  1241. delegate()->peerListRefreshRows();
  1242. }).fail([this] {
  1243. _loadRequestId = 0;
  1244. }).send();
  1245. }
  1246. void AddSpecialBoxController::rowClicked(not_null<PeerListRow*> row) {
  1247. const auto participant = row->peer();
  1248. const auto user = participant->asUser();
  1249. switch (_role) {
  1250. case Role::Admins:
  1251. Assert(user != nullptr);
  1252. return showAdmin(user);
  1253. case Role::Restricted:
  1254. Assert(user != nullptr);
  1255. return showRestricted(user);
  1256. case Role::Kicked: return kickUser(participant);
  1257. }
  1258. Unexpected("Role in AddSpecialBoxController::rowClicked()");
  1259. }
  1260. template <typename Callback>
  1261. bool AddSpecialBoxController::checkInfoLoaded(
  1262. not_null<PeerData*> participant,
  1263. Callback callback) {
  1264. if (_additional.infoLoaded(participant)) {
  1265. return true;
  1266. }
  1267. // We don't know what this user status is in the group.
  1268. const auto channel = _peer->asChannel();
  1269. _api.request(MTPchannels_GetParticipant(
  1270. channel->inputChannel,
  1271. participant->input
  1272. )).done([=](const MTPchannels_ChannelParticipant &result) {
  1273. result.match([&](const MTPDchannels_channelParticipant &data) {
  1274. channel->owner().processUsers(data.vusers());
  1275. _additional.applyParticipant(
  1276. Api::ChatParticipant(data.vparticipant(), channel));
  1277. });
  1278. callback();
  1279. }).fail([=] {
  1280. _additional.setExternal(participant);
  1281. callback();
  1282. }).send();
  1283. return false;
  1284. }
  1285. void AddSpecialBoxController::showAdmin(
  1286. not_null<UserData*> user,
  1287. bool sure) {
  1288. if (!checkInfoLoaded(user, [=] { showAdmin(user); })) {
  1289. return;
  1290. }
  1291. _editBox = nullptr;
  1292. if (_editParticipantBox) {
  1293. _editParticipantBox->closeBox();
  1294. }
  1295. const auto chat = _peer->asChat();
  1296. const auto channel = _peer->asChannel();
  1297. const auto showAdminSure = crl::guard(this, [=] {
  1298. showAdmin(user, true);
  1299. });
  1300. // Check restrictions.
  1301. const auto canAddMembers = chat
  1302. ? chat->canAddMembers()
  1303. : channel->canAddMembers();
  1304. const auto canBanMembers = chat
  1305. ? chat->canBanMembers()
  1306. : channel->canBanMembers();
  1307. const auto adminRights = _additional.adminRights(user);
  1308. if (adminRights.has_value()) {
  1309. // The user is already an admin.
  1310. } else if (_additional.isKicked(user)) {
  1311. // The user is banned.
  1312. if (canAddMembers) {
  1313. if (canBanMembers) {
  1314. if (!sure) {
  1315. _editBox = showBox(
  1316. Ui::MakeConfirmBox({
  1317. tr::lng_sure_add_admin_unremove(),
  1318. showAdminSure
  1319. }));
  1320. return;
  1321. }
  1322. } else {
  1323. showBox(
  1324. Ui::MakeInformBox(tr::lng_error_cant_add_admin_unban()));
  1325. return;
  1326. }
  1327. } else {
  1328. showBox(Ui::MakeInformBox(tr::lng_error_cant_add_admin_invite()));
  1329. return;
  1330. }
  1331. } else if (_additional.restrictedRights(user).has_value()) {
  1332. // The user is restricted.
  1333. if (canBanMembers) {
  1334. if (!sure) {
  1335. _editBox = showBox(
  1336. Ui::MakeConfirmBox({
  1337. tr::lng_sure_add_admin_unremove(),
  1338. showAdminSure
  1339. }));
  1340. return;
  1341. }
  1342. } else {
  1343. showBox(Ui::MakeInformBox(tr::lng_error_cant_add_admin_unban()));
  1344. return;
  1345. }
  1346. } else if (_additional.isExternal(user)) {
  1347. // The user is not in the group yet.
  1348. if (canAddMembers) {
  1349. if (!sure) {
  1350. auto text = ((_peer->isChat() || _peer->isMegagroup())
  1351. ? tr::lng_sure_add_admin_invite
  1352. : tr::lng_sure_add_admin_invite_channel)();
  1353. _editBox = showBox(
  1354. Ui::MakeConfirmBox({
  1355. std::move(text),
  1356. showAdminSure
  1357. }));
  1358. return;
  1359. }
  1360. } else {
  1361. showBox(Ui::MakeInformBox(tr::lng_error_cant_add_admin_invite()));
  1362. return;
  1363. }
  1364. }
  1365. // Finally show the admin.
  1366. const auto currentRights = adminRights
  1367. ? *adminRights
  1368. : ChatAdminRightsInfo();
  1369. auto box = Box<EditAdminBox>(
  1370. _peer,
  1371. user,
  1372. currentRights,
  1373. _additional.adminRank(user),
  1374. _additional.adminPromotedSince(user),
  1375. _additional.adminPromotedBy(user));
  1376. const auto show = delegate()->peerListUiShow();
  1377. if (_additional.canAddOrEditAdmin(user)) {
  1378. const auto done = crl::guard(this, [=](
  1379. ChatAdminRightsInfo newRights,
  1380. const QString &rank) {
  1381. editAdminDone(user, newRights, rank);
  1382. });
  1383. const auto fail = crl::guard(this, [=] {
  1384. if (_editParticipantBox) {
  1385. _editParticipantBox->closeBox();
  1386. }
  1387. });
  1388. box->setSaveCallback(
  1389. SaveAdminCallback(show, _peer, user, done, fail));
  1390. }
  1391. _editParticipantBox = showBox(std::move(box));
  1392. }
  1393. void AddSpecialBoxController::editAdminDone(
  1394. not_null<UserData*> user,
  1395. ChatAdminRightsInfo rights,
  1396. const QString &rank) {
  1397. if (_editParticipantBox) {
  1398. _editParticipantBox->closeBox();
  1399. }
  1400. _additional.applyAdminLocally(user, rights, rank);
  1401. if (const auto callback = _adminDoneCallback) {
  1402. callback(user, rights, rank);
  1403. }
  1404. }
  1405. void AddSpecialBoxController::showRestricted(
  1406. not_null<UserData*> user,
  1407. bool sure) {
  1408. if (!checkInfoLoaded(user, [=] { showRestricted(user); })) {
  1409. return;
  1410. }
  1411. _editBox = nullptr;
  1412. if (_editParticipantBox) {
  1413. _editParticipantBox->closeBox();
  1414. }
  1415. const auto showRestrictedSure = crl::guard(this, [=] {
  1416. showRestricted(user, true);
  1417. });
  1418. // Check restrictions.
  1419. const auto restrictedRights = _additional.restrictedRights(user);
  1420. if (restrictedRights.has_value()) {
  1421. // The user is already banned or restricted.
  1422. } else if (_additional.adminRights(user).has_value()
  1423. || _additional.isCreator(user)) {
  1424. // The user is an admin or creator.
  1425. if (!_additional.isCreator(user) && _additional.canEditAdmin(user)) {
  1426. if (!sure) {
  1427. _editBox = showBox(
  1428. Ui::MakeConfirmBox({
  1429. tr::lng_sure_ban_admin(),
  1430. showRestrictedSure
  1431. }));
  1432. return;
  1433. }
  1434. } else {
  1435. showBox(Ui::MakeInformBox(tr::lng_error_cant_ban_admin()));
  1436. return;
  1437. }
  1438. }
  1439. // Finally edit the restricted.
  1440. const auto currentRights = restrictedRights
  1441. ? *restrictedRights
  1442. : ChatRestrictionsInfo();
  1443. auto box = Box<EditRestrictedBox>(
  1444. _peer,
  1445. user,
  1446. _additional.adminRights(user).has_value(),
  1447. currentRights,
  1448. _additional.restrictedBy(user),
  1449. _additional.restrictedSince(user));
  1450. if (_additional.canRestrictParticipant(user)) {
  1451. const auto done = crl::guard(this, [=](
  1452. ChatRestrictionsInfo newRights) {
  1453. editRestrictedDone(user, newRights);
  1454. });
  1455. const auto fail = crl::guard(this, [=] {
  1456. if (_editParticipantBox) {
  1457. _editParticipantBox->closeBox();
  1458. }
  1459. });
  1460. box->setSaveCallback(
  1461. SaveRestrictedCallback(_peer, user, done, fail));
  1462. }
  1463. _editParticipantBox = showBox(std::move(box));
  1464. }
  1465. void AddSpecialBoxController::editRestrictedDone(
  1466. not_null<PeerData*> participant,
  1467. ChatRestrictionsInfo rights) {
  1468. if (_editParticipantBox) {
  1469. _editParticipantBox->closeBox();
  1470. }
  1471. _additional.applyBannedLocally(participant, rights);
  1472. if (const auto callback = _bannedDoneCallback) {
  1473. callback(participant, rights);
  1474. }
  1475. }
  1476. void AddSpecialBoxController::kickUser(
  1477. not_null<PeerData*> participant,
  1478. bool sure) {
  1479. if (!checkInfoLoaded(participant, [=] { kickUser(participant); })) {
  1480. return;
  1481. }
  1482. const auto kickUserSure = crl::guard(this, [=] {
  1483. kickUser(participant, true);
  1484. });
  1485. // Check restrictions.
  1486. const auto user = participant->asUser();
  1487. if (user && (_additional.adminRights(user).has_value()
  1488. || (_additional.isCreator(user)))) {
  1489. // The user is an admin or creator.
  1490. if (!_additional.isCreator(user) && _additional.canEditAdmin(user)) {
  1491. if (!sure) {
  1492. _editBox = showBox(
  1493. Ui::MakeConfirmBox({
  1494. tr::lng_sure_ban_admin(),
  1495. kickUserSure
  1496. }));
  1497. return;
  1498. }
  1499. } else {
  1500. showBox(Ui::MakeInformBox(tr::lng_error_cant_ban_admin()));
  1501. return;
  1502. }
  1503. }
  1504. // Finally kick him.
  1505. if (!sure) {
  1506. const auto text = ((_peer->isChat() || _peer->isMegagroup())
  1507. ? tr::lng_profile_sure_kick
  1508. : tr::lng_profile_sure_kick_channel)(
  1509. tr::now,
  1510. lt_user,
  1511. participant->name());
  1512. _editBox = showBox(Ui::MakeConfirmBox({ text, kickUserSure }));
  1513. return;
  1514. }
  1515. const auto restrictedRights = _additional.restrictedRights(participant);
  1516. const auto currentRights = restrictedRights
  1517. ? *restrictedRights
  1518. : ChatRestrictionsInfo();
  1519. const auto done = crl::guard(this, [=](
  1520. ChatRestrictionsInfo newRights) {
  1521. editRestrictedDone(participant, newRights);
  1522. });
  1523. const auto fail = crl::guard(this, [=] {
  1524. _editBox = nullptr;
  1525. });
  1526. const auto callback = SaveRestrictedCallback(
  1527. _peer,
  1528. participant,
  1529. done,
  1530. fail);
  1531. callback(currentRights, ChannelData::KickedRestrictedRights(participant));
  1532. }
  1533. bool AddSpecialBoxController::appendRow(not_null<PeerData*> participant) {
  1534. if (delegate()->peerListFindRow(participant->id.value)
  1535. || (_excludeSelf && participant->isSelf())) {
  1536. return false;
  1537. }
  1538. delegate()->peerListAppendRow(createRow(participant));
  1539. return true;
  1540. }
  1541. bool AddSpecialBoxController::prependRow(not_null<UserData*> user) {
  1542. if (delegate()->peerListFindRow(user->id.value)) {
  1543. return false;
  1544. }
  1545. delegate()->peerListPrependRow(createRow(user));
  1546. return true;
  1547. }
  1548. std::unique_ptr<PeerListRow> AddSpecialBoxController::createRow(
  1549. not_null<PeerData*> participant) const {
  1550. return std::make_unique<PeerListRow>(participant);
  1551. }
  1552. AddSpecialBoxSearchController::AddSpecialBoxSearchController(
  1553. not_null<PeerData*> peer,
  1554. not_null<ParticipantsAdditionalData*> additional)
  1555. : _peer(peer)
  1556. , _additional(additional)
  1557. , _api(&_peer->session().mtp())
  1558. , _timer([=] { searchOnServer(); }) {
  1559. subscribeToMigration();
  1560. }
  1561. void AddSpecialBoxSearchController::subscribeToMigration() {
  1562. SubscribeToMigration(
  1563. _peer,
  1564. lifetime(),
  1565. [=](not_null<ChannelData*> channel) { _peer = channel; });
  1566. }
  1567. void AddSpecialBoxSearchController::searchQuery(const QString &query) {
  1568. if (_query != query) {
  1569. _query = query;
  1570. _offset = 0;
  1571. _requestId = 0;
  1572. _participantsLoaded = false;
  1573. _chatsContactsAdded = false;
  1574. _chatMembersAdded = false;
  1575. _globalLoaded = false;
  1576. if (!_query.isEmpty() && !searchParticipantsInCache()) {
  1577. _timer.callOnce(AutoSearchTimeout);
  1578. } else {
  1579. _timer.cancel();
  1580. }
  1581. }
  1582. }
  1583. void AddSpecialBoxSearchController::searchOnServer() {
  1584. Expects(!_query.isEmpty());
  1585. loadMoreRows();
  1586. }
  1587. bool AddSpecialBoxSearchController::isLoading() {
  1588. return _timer.isActive() || _requestId;
  1589. }
  1590. bool AddSpecialBoxSearchController::searchParticipantsInCache() {
  1591. const auto i = _participantsCache.find(_query);
  1592. if (i != _participantsCache.cend()) {
  1593. _requestId = 0;
  1594. searchParticipantsDone(
  1595. _requestId,
  1596. i->second.result,
  1597. i->second.requestedCount);
  1598. return true;
  1599. }
  1600. return false;
  1601. }
  1602. bool AddSpecialBoxSearchController::searchGlobalInCache() {
  1603. auto it = _globalCache.find(_query);
  1604. if (it != _globalCache.cend()) {
  1605. _requestId = 0;
  1606. searchGlobalDone(_requestId, it->second);
  1607. return true;
  1608. }
  1609. return false;
  1610. }
  1611. bool AddSpecialBoxSearchController::loadMoreRows() {
  1612. if (_query.isEmpty()) {
  1613. return false;
  1614. }
  1615. if (_globalLoaded) {
  1616. return true;
  1617. }
  1618. if (_participantsLoaded || _chatMembersAdded) {
  1619. if (!_chatsContactsAdded) {
  1620. addChatsContacts();
  1621. }
  1622. if (!isLoading() && !searchGlobalInCache()) {
  1623. requestGlobal();
  1624. }
  1625. } else if (const auto chat = _peer->asChat()) {
  1626. if (!_chatMembersAdded) {
  1627. addChatMembers(chat);
  1628. }
  1629. } else if (!isLoading()) {
  1630. requestParticipants();
  1631. }
  1632. return true;
  1633. }
  1634. void AddSpecialBoxSearchController::requestParticipants() {
  1635. Expects(_peer->isChannel());
  1636. // For search we request a lot of rows from the first query.
  1637. // (because we've waited for search request by timer already,
  1638. // so we don't expect it to be fast, but we want to fill cache).
  1639. const auto perPage = kParticipantsPerPage;
  1640. const auto participantsHash = uint64(0);
  1641. const auto channel = _peer->asChannel();
  1642. _requestId = _api.request(MTPchannels_GetParticipants(
  1643. channel->inputChannel,
  1644. MTP_channelParticipantsSearch(MTP_string(_query)),
  1645. MTP_int(_offset),
  1646. MTP_int(perPage),
  1647. MTP_long(participantsHash)
  1648. )).done([=](
  1649. const MTPchannels_ChannelParticipants &result,
  1650. mtpRequestId requestId) {
  1651. searchParticipantsDone(requestId, result, perPage);
  1652. }).fail([=](const MTP::Error &error, mtpRequestId requestId) {
  1653. if (_requestId == requestId) {
  1654. _requestId = 0;
  1655. _participantsLoaded = true;
  1656. loadMoreRows();
  1657. delegate()->peerListSearchRefreshRows();
  1658. }
  1659. }).send();
  1660. auto entry = Query();
  1661. entry.text = _query;
  1662. entry.offset = _offset;
  1663. _participantsQueries.emplace(_requestId, entry);
  1664. }
  1665. void AddSpecialBoxSearchController::searchParticipantsDone(
  1666. mtpRequestId requestId,
  1667. const MTPchannels_ChannelParticipants &result,
  1668. int requestedCount) {
  1669. Expects(_peer->isChannel());
  1670. const auto channel = _peer->asChannel();
  1671. auto query = _query;
  1672. if (requestId) {
  1673. const auto addToCache = [&] {
  1674. auto it = _participantsQueries.find(requestId);
  1675. if (it != _participantsQueries.cend()) {
  1676. query = it->second.text;
  1677. if (it->second.offset == 0) {
  1678. auto &entry = _participantsCache[query];
  1679. entry.result = result;
  1680. entry.requestedCount = requestedCount;
  1681. }
  1682. _participantsQueries.erase(it);
  1683. }
  1684. };
  1685. result.match([&](const MTPDchannels_channelParticipants &data) {
  1686. Api::ChatParticipants::Parse(channel, data);
  1687. addToCache();
  1688. }, [&](const MTPDchannels_channelParticipantsNotModified &) {
  1689. LOG(("API Error: "
  1690. "channels.channelParticipantsNotModified received!"));
  1691. });
  1692. }
  1693. if (_requestId != requestId) {
  1694. return;
  1695. }
  1696. _requestId = 0;
  1697. result.match([&](const MTPDchannels_channelParticipants &data) {
  1698. const auto &list = data.vparticipants().v;
  1699. if (list.size() < requestedCount) {
  1700. // We want cache to have full information about a query with
  1701. // small results count (that we don't need the second request).
  1702. // So we don't wait for empty list unlike the non-search case.
  1703. _participantsLoaded = true;
  1704. if (list.empty() && _offset == 0) {
  1705. // No results, request global search immediately.
  1706. loadMoreRows();
  1707. }
  1708. }
  1709. for (const auto &data : list) {
  1710. if (const auto user = _additional->applyParticipant(
  1711. Api::ChatParticipant(data, channel))) {
  1712. delegate()->peerListSearchAddRow(user);
  1713. }
  1714. }
  1715. _offset += list.size();
  1716. }, [&](const MTPDchannels_channelParticipantsNotModified &) {
  1717. _participantsLoaded = true;
  1718. });
  1719. delegate()->peerListSearchRefreshRows();
  1720. }
  1721. void AddSpecialBoxSearchController::requestGlobal() {
  1722. if (_query.isEmpty()) {
  1723. _globalLoaded = true;
  1724. return;
  1725. }
  1726. auto perPage = SearchPeopleLimit;
  1727. _requestId = _api.request(MTPcontacts_Search(
  1728. MTP_string(_query),
  1729. MTP_int(perPage)
  1730. )).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) {
  1731. searchGlobalDone(requestId, result);
  1732. }).fail([=](const MTP::Error &error, mtpRequestId requestId) {
  1733. if (_requestId == requestId) {
  1734. _requestId = 0;
  1735. _globalLoaded = true;
  1736. delegate()->peerListSearchRefreshRows();
  1737. }
  1738. }).send();
  1739. _globalQueries.emplace(_requestId, _query);
  1740. }
  1741. void AddSpecialBoxSearchController::searchGlobalDone(
  1742. mtpRequestId requestId,
  1743. const MTPcontacts_Found &result) {
  1744. Expects(result.type() == mtpc_contacts_found);
  1745. auto &found = result.c_contacts_found();
  1746. auto query = _query;
  1747. if (requestId) {
  1748. _peer->owner().processUsers(found.vusers());
  1749. _peer->owner().processChats(found.vchats());
  1750. auto it = _globalQueries.find(requestId);
  1751. if (it != _globalQueries.cend()) {
  1752. query = it->second;
  1753. _globalCache[query] = result;
  1754. _globalQueries.erase(it);
  1755. }
  1756. }
  1757. const auto feedList = [&](const MTPVector<MTPPeer> &list) {
  1758. for (const auto &mtpPeer : list.v) {
  1759. const auto peerId = peerFromMTP(mtpPeer);
  1760. if (const auto peer = _peer->owner().peerLoaded(peerId)) {
  1761. if (const auto user = peer->asUser()) {
  1762. _additional->checkForLoaded(user);
  1763. delegate()->peerListSearchAddRow(user);
  1764. }
  1765. }
  1766. }
  1767. };
  1768. if (_requestId == requestId) {
  1769. _requestId = 0;
  1770. _globalLoaded = true;
  1771. feedList(found.vmy_results());
  1772. feedList(found.vresults());
  1773. delegate()->peerListSearchRefreshRows();
  1774. }
  1775. }
  1776. void AddSpecialBoxSearchController::addChatMembers(
  1777. not_null<ChatData*> chat) {
  1778. if (chat->participants.empty()) {
  1779. return;
  1780. }
  1781. _chatMembersAdded = true;
  1782. const auto wordList = TextUtilities::PrepareSearchWords(_query);
  1783. if (wordList.empty()) {
  1784. return;
  1785. }
  1786. const auto allWordsAreFound = [&](
  1787. const base::flat_set<QString> &nameWords) {
  1788. const auto hasNamePartStartingWith = [&](const QString &word) {
  1789. for (const auto &nameWord : nameWords) {
  1790. if (nameWord.startsWith(word)) {
  1791. return true;
  1792. }
  1793. }
  1794. return false;
  1795. };
  1796. for (const auto &word : wordList) {
  1797. if (!hasNamePartStartingWith(word)) {
  1798. return false;
  1799. }
  1800. }
  1801. return true;
  1802. };
  1803. for (const auto &user : chat->participants) {
  1804. if (allWordsAreFound(user->nameWords())) {
  1805. delegate()->peerListSearchAddRow(user);
  1806. }
  1807. }
  1808. delegate()->peerListSearchRefreshRows();
  1809. }
  1810. void AddSpecialBoxSearchController::addChatsContacts() {
  1811. _chatsContactsAdded = true;
  1812. const auto wordList = TextUtilities::PrepareSearchWords(_query);
  1813. if (wordList.empty()) {
  1814. return;
  1815. }
  1816. const auto allWordsAreFound = [&](
  1817. const base::flat_set<QString> &nameWords) {
  1818. const auto hasNamePartStartingWith = [&](const QString &word) {
  1819. for (const auto &nameWord : nameWords) {
  1820. if (nameWord.startsWith(word)) {
  1821. return true;
  1822. }
  1823. }
  1824. return false;
  1825. };
  1826. for (const auto &word : wordList) {
  1827. if (!hasNamePartStartingWith(word)) {
  1828. return false;
  1829. }
  1830. }
  1831. return true;
  1832. };
  1833. const auto getSmallestIndex = [&](not_null<Dialogs::IndexedList*> list)
  1834. -> const Dialogs::List* {
  1835. if (list->empty()) {
  1836. return nullptr;
  1837. }
  1838. auto result = (const Dialogs::List*)nullptr;
  1839. for (const auto &word : wordList) {
  1840. const auto found = list->filtered(word[0]);
  1841. if (!found || found->empty()) {
  1842. return nullptr;
  1843. }
  1844. if (!result || result->size() > found->size()) {
  1845. result = found;
  1846. }
  1847. }
  1848. return result;
  1849. };
  1850. const auto filterAndAppend = [&](not_null<Dialogs::IndexedList*> list) {
  1851. const auto index = getSmallestIndex(list);
  1852. if (!index) {
  1853. return;
  1854. }
  1855. for (const auto &row : *index) {
  1856. if (const auto history = row->history()) {
  1857. if (const auto user = history->peer->asUser()) {
  1858. if (allWordsAreFound(user->nameWords())) {
  1859. delegate()->peerListSearchAddRow(user);
  1860. }
  1861. }
  1862. }
  1863. }
  1864. };
  1865. filterAndAppend(_peer->owner().chatsList()->indexed());
  1866. const auto id = Data::Folder::kId;
  1867. if (const auto folder = _peer->owner().folderLoaded(id)) {
  1868. filterAndAppend(folder->chatsList()->indexed());
  1869. }
  1870. filterAndAppend(_peer->owner().contactsNoChatsList());
  1871. delegate()->peerListSearchRefreshRows();
  1872. }