choose_peer_box.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  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/choose_peer_box.h"
  8. #include "apiwrap.h" // ApiWrap::botCommonGroups / requestBotCommonGroups.
  9. #include "boxes/add_contact_box.h"
  10. #include "boxes/peer_list_controllers.h"
  11. #include "boxes/premium_limits_box.h"
  12. #include "data/data_chat.h"
  13. #include "data/data_channel.h"
  14. #include "data/data_peer.h"
  15. #include "data/data_user.h"
  16. #include "history/history.h"
  17. #include "history/history_item_reply_markup.h"
  18. #include "info/profile/info_profile_icon.h"
  19. #include "lang/lang_keys.h"
  20. #include "main/main_session.h" // Session::api().
  21. #include "ui/boxes/confirm_box.h"
  22. #include "ui/text/text_utilities.h"
  23. #include "ui/widgets/buttons.h"
  24. #include "ui/wrap/vertical_layout.h"
  25. #include "ui/vertical_list.h"
  26. #include "window/window_session_controller.h"
  27. #include "styles/style_boxes.h"
  28. #include "styles/style_chat_helpers.h"
  29. #include "styles/style_layers.h"
  30. namespace {
  31. class ChoosePeerBoxController final
  32. : public ChatsListBoxController
  33. , public base::has_weak_ptr {
  34. public:
  35. ChoosePeerBoxController(
  36. not_null<Window::SessionNavigation*> navigation,
  37. not_null<UserData*> bot,
  38. RequestPeerQuery query,
  39. Fn<void(std::vector<not_null<PeerData*>>)> callback);
  40. Main::Session &session() const override;
  41. void rowClicked(not_null<PeerListRow*> row) override;
  42. [[nodiscard]] rpl::producer<int> selectedCountValue() const;
  43. void submit();
  44. QString savedMessagesChatStatus() const override {
  45. return {};
  46. }
  47. private:
  48. void prepareViewHook() override;
  49. std::unique_ptr<Row> createRow(not_null<History*> history) override;
  50. QString emptyBoxText() const override;
  51. void prepareRestrictions();
  52. const not_null<Window::SessionNavigation*> _navigation;
  53. not_null<UserData*> _bot;
  54. RequestPeerQuery _query;
  55. base::flat_set<not_null<PeerData*>> _commonGroups;
  56. base::flat_set<not_null<PeerData*>> _selected;
  57. rpl::variable<int> _selectedCount;
  58. Fn<void(std::vector<not_null<PeerData*>>)> _callback;
  59. };
  60. using RightsMap = std::vector<std::pair<ChatAdminRight, tr::phrase<>>>;
  61. [[nodiscard]] RightsMap GroupRights() {
  62. using Flag = ChatAdminRight;
  63. return {
  64. { Flag::ChangeInfo, tr::lng_request_group_change_info },
  65. {
  66. Flag::DeleteMessages,
  67. tr::lng_request_group_delete_messages },
  68. { Flag::BanUsers, tr::lng_request_group_ban_users },
  69. { Flag::InviteByLinkOrAdd, tr::lng_request_group_invite },
  70. { Flag::PinMessages, tr::lng_request_group_pin_messages },
  71. { Flag::ManageTopics, tr::lng_request_group_manage_topics },
  72. {
  73. Flag::ManageCall,
  74. tr::lng_request_group_manage_video_chats },
  75. { Flag::Anonymous, tr::lng_request_group_anonymous },
  76. { Flag::AddAdmins, tr::lng_request_group_add_admins },
  77. };
  78. }
  79. [[nodiscard]] RightsMap BroadcastRights() {
  80. using Flag = ChatAdminRight;
  81. return {
  82. { Flag::ChangeInfo, tr::lng_request_channel_change_info },
  83. {
  84. Flag::PostMessages,
  85. tr::lng_request_channel_post_messages },
  86. {
  87. Flag::EditMessages,
  88. tr::lng_request_channel_edit_messages },
  89. {
  90. Flag::DeleteMessages,
  91. tr::lng_request_channel_delete_messages },
  92. {
  93. Flag::InviteByLinkOrAdd,
  94. tr::lng_request_channel_add_subscribers },
  95. {
  96. Flag::ManageCall,
  97. tr::lng_request_channel_manage_livestreams },
  98. { Flag::AddAdmins, tr::lng_request_channel_add_admins },
  99. };
  100. }
  101. [[nodiscard]] QString RightsText(
  102. ChatAdminRights rights,
  103. const RightsMap &phrases) {
  104. auto list = QStringList();
  105. for (const auto &[flag, phrase] : phrases) {
  106. if (rights & flag) {
  107. list.push_back(phrase(tr::now));
  108. }
  109. }
  110. const auto count = list.size();
  111. if (!count) {
  112. return QString();
  113. }
  114. const auto last = list.back();
  115. return (count > 1)
  116. ? tr::lng_request_peer_rights_and(
  117. tr::now,
  118. lt_rights,
  119. list.mid(0, count - 1).join(", "),
  120. lt_last,
  121. last)
  122. : last;
  123. }
  124. [[nodiscard]] QString GroupRightsText(ChatAdminRights rights) {
  125. return RightsText(rights, GroupRights());
  126. }
  127. [[nodiscard]] QString BroadcastRightsText(ChatAdminRights rights) {
  128. return RightsText(rights, BroadcastRights());
  129. }
  130. [[nodiscard]] QStringList RestrictionsList(RequestPeerQuery query) {
  131. using Type = RequestPeerQuery::Type;
  132. using Restriction = RequestPeerQuery::Restriction;
  133. auto result = QStringList();
  134. const auto addRestriction = [&](
  135. Restriction value,
  136. tr::phrase<> yes,
  137. tr::phrase<> no) {
  138. if (value == Restriction::Yes) {
  139. result.push_back(yes(tr::now));
  140. } else if (value == Restriction::No) {
  141. result.push_back(no(tr::now));
  142. }
  143. };
  144. const auto addRights = [&](const QString &rights) {
  145. if (!rights.isEmpty()) {
  146. result.push_back(
  147. tr::lng_request_peer_rights(tr::now, lt_rights, rights));
  148. }
  149. };
  150. switch (query.type) {
  151. case Type::User:
  152. if (query.userIsBot != Restriction::Yes) {
  153. addRestriction(
  154. query.userIsPremium,
  155. tr::lng_request_user_premium_yes,
  156. tr::lng_request_user_premium_no);
  157. }
  158. break;
  159. case Type::Group:
  160. addRestriction(
  161. query.hasUsername,
  162. tr::lng_request_group_public_yes,
  163. tr::lng_request_group_public_no);
  164. addRestriction(
  165. query.groupIsForum,
  166. tr::lng_request_group_topics_yes,
  167. tr::lng_request_group_topics_no);
  168. if (query.amCreator) {
  169. result.push_back(tr::lng_request_group_am_owner(tr::now));
  170. } else {
  171. addRights(GroupRightsText(query.myRights));
  172. }
  173. break;
  174. case Type::Broadcast:
  175. addRestriction(
  176. query.hasUsername,
  177. tr::lng_request_channel_public_yes,
  178. tr::lng_request_channel_public_no);
  179. if (query.amCreator) {
  180. result.push_back(tr::lng_request_channel_am_owner(tr::now));
  181. } else {
  182. addRights(BroadcastRightsText(query.myRights));
  183. }
  184. break;
  185. }
  186. return result;
  187. }
  188. object_ptr<Ui::BoxContent> MakeConfirmBox(
  189. not_null<UserData*> bot,
  190. not_null<PeerData*> peer,
  191. RequestPeerQuery query,
  192. Fn<void()> confirmed) {
  193. const auto name = peer->name();
  194. const auto botName = bot->name();
  195. auto text = tr::lng_request_peer_confirm(
  196. tr::now,
  197. lt_chat,
  198. Ui::Text::Bold(name),
  199. lt_bot,
  200. Ui::Text::Bold(botName),
  201. Ui::Text::WithEntities);
  202. if (!peer->isUser()) {
  203. const auto rights = peer->isBroadcast()
  204. ? BroadcastRightsText(query.botRights)
  205. : GroupRightsText(query.botRights);
  206. if (!rights.isEmpty()) {
  207. text.append('\n').append('\n').append(
  208. tr::lng_request_peer_confirm_rights(
  209. tr::now,
  210. lt_bot,
  211. Ui::Text::Bold(botName),
  212. lt_chat,
  213. Ui::Text::Bold(name),
  214. lt_rights,
  215. TextWithEntities{ rights },
  216. Ui::Text::WithEntities));
  217. } else if (!peer->isBroadcast() && query.isBotParticipant) {
  218. const auto common = bot->session().api().botCommonGroups(bot);
  219. if (!common || !ranges::contains(*common, peer)) {
  220. text.append('\n').append('\n').append(
  221. tr::lng_request_peer_confirm_add(
  222. tr::now,
  223. lt_bot,
  224. Ui::Text::Bold(botName),
  225. lt_chat,
  226. Ui::Text::Bold(name),
  227. Ui::Text::WithEntities));
  228. }
  229. }
  230. }
  231. return Ui::MakeConfirmBox({
  232. .text = std::move(text),
  233. .confirmed = [=](Fn<void()> close) { confirmed(); close(); },
  234. .confirmText = tr::lng_request_peer_confirm_send(tr::now),
  235. });
  236. }
  237. object_ptr<Ui::BoxContent> CreatePeerByQueryBox(
  238. not_null<Window::SessionNavigation*> navigation,
  239. not_null<UserData*> bot,
  240. RequestPeerQuery query,
  241. Fn<void(std::vector<not_null<PeerData*>>)> done) {
  242. const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
  243. auto callback = [=](not_null<PeerData*> peer) {
  244. done({ peer });
  245. if (const auto strong = weak->data()) {
  246. strong->closeBox();
  247. }
  248. };
  249. auto result = Box<GroupInfoBox>(
  250. navigation,
  251. bot,
  252. query,
  253. std::move(callback));
  254. *weak = result.data();
  255. return result;
  256. }
  257. [[nodiscard]] bool FilterPeerByQuery(
  258. not_null<PeerData*> peer,
  259. RequestPeerQuery query,
  260. const base::flat_set<not_null<PeerData*>> &commonGroups) {
  261. using Type = RequestPeerQuery::Type;
  262. using Restriction = RequestPeerQuery::Restriction;
  263. const auto checkRestriction = [](Restriction restriction, bool value) {
  264. return (restriction == Restriction::Any)
  265. || ((restriction == Restriction::Yes) == value);
  266. };
  267. const auto checkRights = [](
  268. ChatAdminRights wanted,
  269. bool creator,
  270. ChatAdminRights rights) {
  271. return creator || ((rights & wanted) == wanted);
  272. };
  273. switch (query.type) {
  274. case Type::User: {
  275. const auto user = peer->asUser();
  276. return user
  277. && !user->isInaccessible()
  278. && !user->isNotificationsUser()
  279. && checkRestriction(query.userIsBot, user->isBot())
  280. && checkRestriction(query.userIsPremium, user->isPremium());
  281. }
  282. case Type::Group: {
  283. const auto chat = peer->asChat();
  284. const auto megagroup = peer->asMegagroup();
  285. return (chat || megagroup)
  286. && (!query.amCreator
  287. || (chat ? chat->amCreator() : megagroup->amCreator()))
  288. && checkRestriction(query.groupIsForum, peer->isForum())
  289. && checkRestriction(
  290. query.hasUsername,
  291. megagroup && megagroup->hasUsername())
  292. && checkRights(
  293. query.myRights,
  294. chat ? chat->amCreator() : megagroup->amCreator(),
  295. chat ? chat->adminRights() : megagroup->adminRights())
  296. && (!query.isBotParticipant
  297. || query.myRights
  298. || commonGroups.contains(peer)
  299. || (chat
  300. ? chat->canAddMembers()
  301. : megagroup->canAddMembers()));
  302. }
  303. case Type::Broadcast: {
  304. const auto broadcast = peer->asBroadcast();
  305. return broadcast
  306. && (!query.amCreator || broadcast->amCreator())
  307. && checkRestriction(query.hasUsername, broadcast->hasUsername())
  308. && checkRights(
  309. query.myRights,
  310. broadcast->amCreator(),
  311. broadcast->adminRights());
  312. }
  313. }
  314. Unexpected("Type in FilterPeerByQuery.");
  315. }
  316. ChoosePeerBoxController::ChoosePeerBoxController(
  317. not_null<Window::SessionNavigation*> navigation,
  318. not_null<UserData*> bot,
  319. RequestPeerQuery query,
  320. Fn<void(std::vector<not_null<PeerData*>>)> callback)
  321. : ChatsListBoxController(&navigation->session())
  322. , _navigation(navigation)
  323. , _bot(bot)
  324. , _query(query)
  325. , _callback(std::move(callback)) {
  326. if (const auto list = _bot->session().api().botCommonGroups(_bot)) {
  327. _commonGroups = { begin(*list), end(*list) };
  328. }
  329. }
  330. Main::Session &ChoosePeerBoxController::session() const {
  331. return _navigation->session();
  332. }
  333. void ChoosePeerBoxController::prepareRestrictions() {
  334. auto above = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
  335. const auto raw = above.data();
  336. auto rows = RestrictionsList(_query);
  337. if (!rows.empty()) {
  338. Ui::AddSubsectionTitle(
  339. raw,
  340. tr::lng_request_peer_requirements(),
  341. { 0, st::membersMarginTop, 0, 0 });
  342. const auto skip = st::defaultSubsectionTitlePadding.left();
  343. auto separator = QString::fromUtf8("\n\xE2\x80\xA2 ");
  344. raw->add(
  345. object_ptr<Ui::FlatLabel>(
  346. raw,
  347. separator + rows.join(separator),
  348. st::requestPeerRestriction),
  349. { skip, 0, skip, st::membersMarginTop });
  350. Ui::AddDivider(raw);
  351. }
  352. const auto make = [&](tr::phrase<> text, const style::icon &st) {
  353. auto button = raw->add(
  354. object_ptr<Ui::SettingsButton>(
  355. raw,
  356. text(),
  357. st::inviteViaLinkButton),
  358. { 0, st::membersMarginTop, 0, 0 });
  359. const auto icon = Ui::CreateChild<Info::Profile::FloatingIcon>(
  360. button,
  361. st,
  362. QPoint());
  363. button->heightValue(
  364. ) | rpl::start_with_next([=](int height) {
  365. icon->moveToLeft(
  366. st::choosePeerCreateIconLeft,
  367. (height - st::inviteViaLinkIcon.height()) / 2);
  368. }, icon->lifetime());
  369. button->setClickedCallback([=] {
  370. _navigation->parentController()->show(
  371. CreatePeerByQueryBox(_navigation, _bot, _query, _callback));
  372. });
  373. button->events(
  374. ) | rpl::filter([=](not_null<QEvent*> e) {
  375. return (e->type() == QEvent::Enter);
  376. }) | rpl::start_with_next([=] {
  377. delegate()->peerListMouseLeftGeometry();
  378. }, button->lifetime());
  379. return button;
  380. };
  381. if (_query.type == RequestPeerQuery::Type::Group) {
  382. make(tr::lng_request_group_create, st::choosePeerGroupIcon);
  383. } else if (_query.type == RequestPeerQuery::Type::Broadcast) {
  384. make(tr::lng_request_channel_create, st::choosePeerChannelIcon);
  385. }
  386. if (raw->count() > 0) {
  387. delegate()->peerListSetAboveWidget(std::move(above));
  388. }
  389. }
  390. void ChoosePeerBoxController::prepareViewHook() {
  391. delegate()->peerListSetTitle([&] {
  392. using Type = RequestPeerQuery::Type;
  393. using Restriction = RequestPeerQuery::Restriction;
  394. switch (_query.type) {
  395. case Type::User: return (_query.userIsBot == Restriction::Yes)
  396. ? tr::lng_request_bot_title()
  397. : (_query.maxQuantity > 1)
  398. ? tr::lng_request_users_title()
  399. : tr::lng_request_user_title();
  400. case Type::Group: return tr::lng_request_group_title();
  401. case Type::Broadcast: return tr::lng_request_channel_title();
  402. }
  403. Unexpected("Type in RequestPeerQuery.");
  404. }());
  405. prepareRestrictions();
  406. }
  407. void ChoosePeerBoxController::rowClicked(not_null<PeerListRow*> row) {
  408. const auto limit = _query.maxQuantity;
  409. const auto multiselect = (limit > 1);
  410. const auto peer = row->peer();
  411. if (multiselect) {
  412. if (_selected.contains(peer) || _selected.size() < limit) {
  413. delegate()->peerListSetRowChecked(row, !row->checked());
  414. if (row->checked()) {
  415. _selected.emplace(peer);
  416. } else {
  417. _selected.remove(peer);
  418. }
  419. _selectedCount = int(_selected.size());
  420. }
  421. return;
  422. }
  423. const auto done = [callback = _callback, peer] {
  424. const auto onstack = callback;
  425. onstack({ peer });
  426. };
  427. if (const auto user = peer->asUser()) {
  428. done();
  429. } else {
  430. delegate()->peerListUiShow()->showBox(
  431. MakeConfirmBox(_bot, peer, _query, done));
  432. }
  433. }
  434. rpl::producer<int> ChoosePeerBoxController::selectedCountValue() const {
  435. return _selectedCount.value();
  436. }
  437. void ChoosePeerBoxController::submit() {
  438. const auto onstack = _callback;
  439. onstack(ranges::to_vector(_selected));
  440. }
  441. auto ChoosePeerBoxController::createRow(not_null<History*> history)
  442. -> std::unique_ptr<Row> {
  443. return FilterPeerByQuery(history->peer, _query, _commonGroups)
  444. ? std::make_unique<Row>(history)
  445. : nullptr;
  446. }
  447. QString ChoosePeerBoxController::emptyBoxText() const {
  448. using Type = RequestPeerQuery::Type;
  449. using Restriction = RequestPeerQuery::Restriction;
  450. const auto result = [](tr::phrase<> title, tr::phrase<> text) {
  451. return title(tr::now) + "\n\n" + text(tr::now);
  452. };
  453. switch (_query.type) {
  454. case Type::User: return (_query.userIsBot == Restriction::Yes)
  455. ? result(tr::lng_request_bot_no, tr::lng_request_bot_no_about)
  456. : result(tr::lng_request_user_no, tr::lng_request_user_no_about);
  457. case Type::Group:
  458. return result(
  459. tr::lng_request_group_no,
  460. tr::lng_request_group_no_about);
  461. case Type::Broadcast:
  462. return result(
  463. tr::lng_request_channel_no,
  464. tr::lng_request_channel_no_about);
  465. }
  466. Unexpected("Type in ChoosePeerBoxController::emptyBoxText.");
  467. }
  468. } // namespace
  469. void ShowChoosePeerBox(
  470. not_null<Window::SessionNavigation*> navigation,
  471. not_null<UserData*> bot,
  472. RequestPeerQuery query,
  473. Fn<void(std::vector<not_null<PeerData*>>)> chosen) {
  474. const auto needCommonGroups = query.isBotParticipant
  475. && (query.type == RequestPeerQuery::Type::Group)
  476. && !query.myRights;
  477. if (needCommonGroups && !bot->session().api().botCommonGroups(bot)) {
  478. const auto weak = base::make_weak(navigation);
  479. bot->session().api().requestBotCommonGroups(bot, [=] {
  480. if (const auto strong = weak.get()) {
  481. ShowChoosePeerBox(strong, bot, query, chosen);
  482. }
  483. });
  484. return;
  485. }
  486. const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
  487. auto callback = [=, done = std::move(chosen)](
  488. std::vector<not_null<PeerData*>> peers) {
  489. done(std::move(peers));
  490. if (const auto strong = weak->data()) {
  491. strong->closeBox();
  492. }
  493. };
  494. const auto limit = query.maxQuantity;
  495. auto controller = std::make_unique<ChoosePeerBoxController>(
  496. navigation,
  497. bot,
  498. query,
  499. std::move(callback));
  500. auto initBox = [=, ptr = controller.get()](not_null<PeerListBox*> box) {
  501. ptr->selectedCountValue() | rpl::start_with_next([=](int count) {
  502. box->clearButtons();
  503. if (limit > 1) {
  504. box->setAdditionalTitle(rpl::single(u"%1 / %2"_q.arg(count).arg(limit)));
  505. }
  506. if (count > 0) {
  507. box->addButton(tr::lng_intro_submit(), [=] {
  508. ptr->submit();
  509. if (*weak) {
  510. (*weak)->closeBox();
  511. }
  512. });
  513. }
  514. box->addButton(tr::lng_cancel(), [box] {
  515. box->closeBox();
  516. });
  517. }, box->lifetime());
  518. };
  519. *weak = navigation->parentController()->show(Box<PeerListBox>(
  520. std::move(controller),
  521. std::move(initBox)));
  522. }