edit_participant_box.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
  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/edit_participant_box.h"
  8. #include "lang/lang_keys.h"
  9. #include "ui/controls/userpic_button.h"
  10. #include "ui/vertical_list.h"
  11. #include "ui/wrap/vertical_layout.h"
  12. #include "ui/wrap/padding_wrap.h"
  13. #include "ui/wrap/slide_wrap.h"
  14. #include "ui/widgets/checkbox.h"
  15. #include "ui/widgets/labels.h"
  16. #include "ui/widgets/buttons.h"
  17. #include "ui/widgets/fields/input_field.h"
  18. #include "ui/widgets/box_content_divider.h"
  19. #include "ui/layers/generic_box.h"
  20. #include "ui/toast/toast.h"
  21. #include "ui/text/text_utilities.h"
  22. #include "ui/text/text_options.h"
  23. #include "ui/painter.h"
  24. #include "chat_helpers/emoji_suggestions_widget.h"
  25. #include "settings/settings_privacy_security.h"
  26. #include "ui/boxes/choose_date_time.h"
  27. #include "ui/boxes/confirm_box.h"
  28. #include "boxes/passcode_box.h"
  29. #include "boxes/peers/add_bot_to_chat_box.h"
  30. #include "boxes/peers/edit_peer_permissions_box.h"
  31. #include "boxes/peers/edit_peer_info_box.h"
  32. #include "data/data_peer_values.h"
  33. #include "data/data_channel.h"
  34. #include "data/data_chat.h"
  35. #include "data/data_user.h"
  36. #include "core/core_cloud_password.h"
  37. #include "base/unixtime.h"
  38. #include "apiwrap.h"
  39. #include "api/api_cloud_password.h"
  40. #include "main/main_session.h"
  41. #include "styles/style_layers.h"
  42. #include "styles/style_boxes.h"
  43. #include "styles/style_info.h"
  44. namespace {
  45. constexpr auto kMaxRestrictDelayDays = 366;
  46. constexpr auto kSecondsInDay = 24 * 60 * 60;
  47. constexpr auto kSecondsInWeek = 7 * kSecondsInDay;
  48. constexpr auto kAdminRoleLimit = 16;
  49. } // namespace
  50. class EditParticipantBox::Inner : public Ui::RpWidget {
  51. public:
  52. Inner(
  53. QWidget *parent,
  54. not_null<PeerData*> peer,
  55. not_null<UserData*> user,
  56. bool hasAdminRights);
  57. template <typename Widget>
  58. Widget *addControl(object_ptr<Widget> widget, QMargins margin);
  59. [[nodiscard]] not_null<Ui::VerticalLayout*> verticalLayout() const {
  60. return _rows;
  61. }
  62. protected:
  63. int resizeGetHeight(int newWidth) override;
  64. void paintEvent(QPaintEvent *e) override;
  65. private:
  66. not_null<PeerData*> _peer;
  67. not_null<UserData*> _user;
  68. object_ptr<Ui::UserpicButton> _userPhoto;
  69. Ui::Text::String _userName;
  70. bool _hasAdminRights = false;
  71. object_ptr<Ui::VerticalLayout> _rows;
  72. };
  73. EditParticipantBox::Inner::Inner(
  74. QWidget *parent,
  75. not_null<PeerData*> peer,
  76. not_null<UserData*> user,
  77. bool hasAdminRights)
  78. : RpWidget(parent)
  79. , _peer(peer)
  80. , _user(user)
  81. , _userPhoto(this, _user, st::rightsPhotoButton)
  82. , _hasAdminRights(hasAdminRights)
  83. , _rows(this) {
  84. _rows->heightValue(
  85. ) | rpl::start_with_next([=] {
  86. resizeToWidth(width());
  87. }, lifetime());
  88. _userPhoto->setAttribute(Qt::WA_TransparentForMouseEvents);
  89. _userName.setText(
  90. st::rightsNameStyle,
  91. _user->name(),
  92. Ui::NameTextOptions());
  93. }
  94. template <typename Widget>
  95. Widget *EditParticipantBox::Inner::addControl(
  96. object_ptr<Widget> widget,
  97. QMargins margin) {
  98. return _rows->add(std::move(widget), margin);
  99. }
  100. int EditParticipantBox::Inner::resizeGetHeight(int newWidth) {
  101. _userPhoto->moveToLeft(
  102. st::rightsPhotoMargin.left(),
  103. st::rightsPhotoMargin.top());
  104. const auto rowsTop = st::rightsPhotoMargin.top()
  105. + st::rightsPhotoButton.size.height()
  106. + st::rightsPhotoMargin.bottom();
  107. _rows->resizeToWidth(newWidth);
  108. _rows->moveToLeft(0, rowsTop, newWidth);
  109. return rowsTop + _rows->heightNoMargins();
  110. }
  111. void EditParticipantBox::Inner::paintEvent(QPaintEvent *e) {
  112. Painter p(this);
  113. p.fillRect(e->rect(), st::boxBg);
  114. p.setPen(st::contactsNameFg);
  115. auto namex = st::rightsPhotoMargin.left()
  116. + st::rightsPhotoButton.size .width()
  117. + st::rightsPhotoMargin.right();
  118. auto namew = width() - namex - st::rightsPhotoMargin.right();
  119. _userName.drawLeftElided(
  120. p,
  121. namex,
  122. st::rightsPhotoMargin.top() + st::rightsNameTop,
  123. namew,
  124. width());
  125. const auto statusText = [&] {
  126. if (_user->isBot()) {
  127. const auto seesAllMessages = _user->botInfo->readsAllHistory
  128. || _hasAdminRights;
  129. return (seesAllMessages
  130. ? tr::lng_status_bot_reads_all
  131. : tr::lng_status_bot_not_reads_all)(tr::now);
  132. }
  133. return Data::OnlineText(_user->lastseen(), base::unixtime::now());
  134. }();
  135. p.setFont(st::contactsStatusFont);
  136. p.setPen(st::contactsStatusFg);
  137. p.drawTextLeft(
  138. namex,
  139. st::rightsPhotoMargin.top() + st::rightsStatusTop,
  140. width(),
  141. statusText);
  142. }
  143. EditParticipantBox::EditParticipantBox(
  144. QWidget*,
  145. not_null<PeerData*> peer,
  146. not_null<UserData*> user,
  147. bool hasAdminRights)
  148. : _peer(peer)
  149. , _user(user)
  150. , _hasAdminRights(hasAdminRights) {
  151. }
  152. not_null<Ui::VerticalLayout*> EditParticipantBox::verticalLayout() const {
  153. return _inner->verticalLayout();
  154. }
  155. void EditParticipantBox::prepare() {
  156. _inner = setInnerWidget(object_ptr<Inner>(
  157. this,
  158. _peer,
  159. _user,
  160. hasAdminRights()));
  161. setDimensionsToContent(st::boxWideWidth, _inner);
  162. }
  163. template <typename Widget>
  164. Widget *EditParticipantBox::addControl(
  165. object_ptr<Widget> widget,
  166. QMargins margin) {
  167. Expects(_inner != nullptr);
  168. return _inner->addControl(std::move(widget), margin);
  169. }
  170. bool EditParticipantBox::amCreator() const {
  171. if (const auto chat = _peer->asChat()) {
  172. return chat->amCreator();
  173. } else if (const auto channel = _peer->asChannel()) {
  174. return channel->amCreator();
  175. }
  176. Unexpected("Peer type in EditParticipantBox::Inner::amCreator.");
  177. }
  178. EditAdminBox::EditAdminBox(
  179. QWidget*,
  180. not_null<PeerData*> peer,
  181. not_null<UserData*> user,
  182. ChatAdminRightsInfo rights,
  183. const QString &rank,
  184. TimeId promotedSince,
  185. UserData *by,
  186. std::optional<EditAdminBotFields> addingBot)
  187. : EditParticipantBox(
  188. nullptr,
  189. peer,
  190. user,
  191. (rights.flags != 0))
  192. , _oldRights(rights)
  193. , _oldRank(rank)
  194. , _promotedSince(promotedSince)
  195. , _by(by)
  196. , _addingBot(std::move(addingBot)) {
  197. }
  198. ChatAdminRightsInfo EditAdminBox::defaultRights() const {
  199. using Flag = ChatAdminRight;
  200. return peer()->isChat()
  201. ? peer()->asChat()->defaultAdminRights(user())
  202. : peer()->isMegagroup()
  203. ? ChatAdminRightsInfo{ (Flag::ChangeInfo
  204. | Flag::DeleteMessages
  205. | Flag::PostStories
  206. | Flag::EditStories
  207. | Flag::DeleteStories
  208. | Flag::BanUsers
  209. | Flag::InviteByLinkOrAdd
  210. | Flag::ManageTopics
  211. | Flag::PinMessages
  212. | Flag::ManageCall) }
  213. : ChatAdminRightsInfo{ (Flag::ChangeInfo
  214. | Flag::PostMessages
  215. | Flag::EditMessages
  216. | Flag::DeleteMessages
  217. | Flag::PostStories
  218. | Flag::EditStories
  219. | Flag::DeleteStories
  220. | Flag::InviteByLinkOrAdd
  221. | Flag::ManageCall) };
  222. }
  223. void EditAdminBox::prepare() {
  224. using namespace rpl::mappers;
  225. using Flag = ChatAdminRight;
  226. using Flags = ChatAdminRights;
  227. EditParticipantBox::prepare();
  228. setTitle(_addingBot
  229. ? (_addingBot->existing
  230. ? tr::lng_rights_edit_admin()
  231. : tr::lng_bot_add_title())
  232. : _oldRights.flags
  233. ? tr::lng_rights_edit_admin()
  234. : tr::lng_channel_add_admin());
  235. if (_addingBot
  236. && !_addingBot->existing
  237. && !peer()->isBroadcast()
  238. && _saveCallback) {
  239. addControl(
  240. object_ptr<Ui::BoxContentDivider>(this),
  241. st::rightsDividerMargin / 2);
  242. _addAsAdmin = addControl(
  243. object_ptr<Ui::Checkbox>(
  244. this,
  245. tr::lng_bot_as_admin_check(tr::now),
  246. st::rightsCheckbox,
  247. std::make_unique<Ui::ToggleView>(
  248. st::rightsToggle,
  249. true)),
  250. st::rightsToggleMargin + (st::rightsDividerMargin / 2));
  251. _addAsAdmin->checkedChanges(
  252. ) | rpl::start_with_next([=](bool checked) {
  253. _adminControlsWrap->toggle(checked, anim::type::normal);
  254. refreshButtons();
  255. }, _addAsAdmin->lifetime());
  256. }
  257. _adminControlsWrap = addControl(
  258. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  259. this,
  260. object_ptr<Ui::VerticalLayout>(this)));
  261. const auto inner = _adminControlsWrap->entity();
  262. if (_promotedSince) {
  263. const auto parsed = base::unixtime::parse(_promotedSince);
  264. const auto label = Ui::AddDividerText(
  265. inner,
  266. tr::lng_rights_about_by(
  267. lt_user,
  268. rpl::single(_by
  269. ? Ui::Text::Link(_by->name(), 1)
  270. : TextWithEntities{ QString::fromUtf8("\U0001F47B") }),
  271. lt_date,
  272. rpl::single(TextWithEntities{ langDateTimeFull(parsed) }),
  273. Ui::Text::WithEntities));
  274. if (_by) {
  275. label->setLink(1, _by->createOpenLink());
  276. }
  277. Ui::AddSkip(inner);
  278. } else {
  279. Ui::AddDivider(inner);
  280. Ui::AddSkip(inner);
  281. }
  282. const auto chat = peer()->asChat();
  283. const auto channel = peer()->asChannel();
  284. const auto prepareRights = _addingBot
  285. ? ChatAdminRightsInfo(_oldRights.flags | _addingBot->existing)
  286. : _oldRights.flags
  287. ? _oldRights
  288. : defaultRights();
  289. const auto disabledByDefaults = (channel && !channel->isMegagroup())
  290. ? ChatAdminRights()
  291. : DisabledByDefaultRestrictions(peer());
  292. const auto filterByMyRights = canSave()
  293. && !_oldRights.flags
  294. && channel
  295. && !channel->amCreator();
  296. const auto prepareFlags = disabledByDefaults
  297. | (prepareRights.flags
  298. & (filterByMyRights ? channel->adminRights() : ~Flag(0)));
  299. const auto disabledMessages = [&] {
  300. auto result = base::flat_map<Flags, QString>();
  301. if (!canSave()) {
  302. result.emplace(
  303. ~Flags(0),
  304. tr::lng_rights_about_admin_cant_edit(tr::now));
  305. } else {
  306. result.emplace(
  307. disabledByDefaults,
  308. tr::lng_rights_permission_for_all(tr::now));
  309. if (amCreator() && user()->isSelf()) {
  310. result.emplace(
  311. ~Flag::Anonymous,
  312. tr::lng_rights_permission_cant_edit(tr::now));
  313. } else if (const auto channel = peer()->asChannel()) {
  314. if (!channel->amCreator()) {
  315. result.emplace(
  316. ~channel->adminRights(),
  317. tr::lng_rights_permission_cant_edit(tr::now));
  318. }
  319. }
  320. }
  321. return result;
  322. }();
  323. const auto isGroup = chat || channel->isMegagroup();
  324. const auto anyoneCanAddMembers = chat
  325. ? chat->anyoneCanAddMembers()
  326. : channel->anyoneCanAddMembers();
  327. const auto options = Data::AdminRightsSetOptions{
  328. .isGroup = isGroup,
  329. .isForum = peer()->isForum(),
  330. .anyoneCanAddMembers = anyoneCanAddMembers,
  331. };
  332. Ui::AddSubsectionTitle(inner, tr::lng_rights_edit_admin_header());
  333. auto [checkboxes, getChecked, changes] = CreateEditAdminRights(
  334. inner,
  335. prepareFlags,
  336. disabledMessages,
  337. options);
  338. inner->add(std::move(checkboxes), QMargins());
  339. auto selectedFlags = rpl::single(
  340. getChecked()
  341. ) | rpl::then(std::move(
  342. changes
  343. ));
  344. const auto hasRank = canSave() && (chat || channel->isMegagroup());
  345. {
  346. const auto aboutAddAdminsInner = inner->add(
  347. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  348. inner,
  349. object_ptr<Ui::VerticalLayout>(inner)));
  350. const auto emptyAboutAddAdminsInner = inner->add(
  351. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  352. inner,
  353. object_ptr<Ui::VerticalLayout>(inner)));
  354. aboutAddAdminsInner->toggle(false, anim::type::instant);
  355. emptyAboutAddAdminsInner->toggle(false, anim::type::instant);
  356. Ui::AddSkip(emptyAboutAddAdminsInner->entity());
  357. if (hasRank) {
  358. Ui::AddDivider(emptyAboutAddAdminsInner->entity());
  359. Ui::AddSkip(emptyAboutAddAdminsInner->entity());
  360. }
  361. Ui::AddSkip(aboutAddAdminsInner->entity());
  362. Ui::AddDividerText(
  363. aboutAddAdminsInner->entity(),
  364. rpl::duplicate(
  365. selectedFlags
  366. ) | rpl::map(
  367. (_1 & Flag::AddAdmins) != 0
  368. ) | rpl::distinct_until_changed(
  369. ) | rpl::map([=](bool canAddAdmins) -> rpl::producer<QString> {
  370. const auto empty = (amCreator() && user()->isSelf());
  371. aboutAddAdminsInner->toggle(!empty, anim::type::instant);
  372. emptyAboutAddAdminsInner->toggle(empty, anim::type::instant);
  373. if (empty) {
  374. return rpl::single(QString());
  375. } else if (!canSave()) {
  376. return tr::lng_rights_about_admin_cant_edit();
  377. } else if (canAddAdmins) {
  378. return tr::lng_rights_about_add_admins_yes();
  379. }
  380. return tr::lng_rights_about_add_admins_no();
  381. }) | rpl::flatten_latest());
  382. }
  383. if (canTransferOwnership()) {
  384. const auto allFlags = AdminRightsForOwnershipTransfer(options);
  385. setupTransferButton(
  386. inner,
  387. isGroup
  388. )->toggleOn(rpl::duplicate(
  389. selectedFlags
  390. ) | rpl::map(
  391. ((_1 & allFlags) == allFlags)
  392. ))->setDuration(0);
  393. }
  394. if (canSave()) {
  395. _rank = hasRank ? addRankInput(inner).get() : nullptr;
  396. _finishSave = [=, value = getChecked] {
  397. const auto newFlags = (value() | ChatAdminRight::Other)
  398. & ((!channel || channel->amCreator())
  399. ? ~Flags(0)
  400. : channel->adminRights());
  401. _saveCallback(
  402. _oldRights,
  403. ChatAdminRightsInfo(newFlags),
  404. _rank ? _rank->getLastText().trimmed() : QString());
  405. };
  406. _save = [=] {
  407. const auto show = uiShow();
  408. if (!_saveCallback) {
  409. return;
  410. } else if (_addAsAdmin && !_addAsAdmin->checked()) {
  411. const auto weak = Ui::MakeWeak(this);
  412. AddBotToGroup(show, user(), peer(), _addingBot->token);
  413. if (const auto strong = weak.data()) {
  414. strong->closeBox();
  415. }
  416. return;
  417. } else if (_addingBot && !_addingBot->existing) {
  418. const auto phrase = peer()->isBroadcast()
  419. ? tr::lng_bot_sure_add_text_channel
  420. : tr::lng_bot_sure_add_text_group;
  421. _confirmBox = getDelegate()->show(Ui::MakeConfirmBox({
  422. phrase(
  423. tr::now,
  424. lt_group,
  425. Ui::Text::Bold(peer()->name()),
  426. Ui::Text::WithEntities),
  427. crl::guard(this, [=] { finishAddAdmin(); })
  428. }));
  429. } else {
  430. _finishSave();
  431. }
  432. };
  433. }
  434. refreshButtons();
  435. }
  436. void EditAdminBox::finishAddAdmin() {
  437. _finishSave();
  438. if (_confirmBox) {
  439. _confirmBox->closeBox();
  440. }
  441. }
  442. void EditAdminBox::refreshButtons() {
  443. clearButtons();
  444. if (canSave()) {
  445. addButton((!_addingBot || _addingBot->existing)
  446. ? tr::lng_settings_save()
  447. : _adminControlsWrap->toggled()
  448. ? tr::lng_bot_add_as_admin()
  449. : tr::lng_bot_add_as_member(), _save);
  450. addButton(tr::lng_cancel(), [=] { closeBox(); });
  451. } else {
  452. addButton(tr::lng_box_ok(), [=] { closeBox(); });
  453. }
  454. }
  455. not_null<Ui::InputField*> EditAdminBox::addRankInput(
  456. not_null<Ui::VerticalLayout*> container) {
  457. // Ui::AddDivider(container);
  458. container->add(
  459. object_ptr<Ui::FlatLabel>(
  460. container,
  461. tr::lng_rights_edit_admin_rank_name(),
  462. st::rightsHeaderLabel),
  463. st::rightsHeaderMargin);
  464. const auto isOwner = [&] {
  465. if (user()->isSelf() && amCreator()) {
  466. return true;
  467. } else if (const auto chat = peer()->asChat()) {
  468. return chat->creator == peerToUser(user()->id);
  469. } else if (const auto channel = peer()->asChannel()) {
  470. return channel->mgInfo && channel->mgInfo->creator == user();
  471. }
  472. Unexpected("Peer type in EditAdminBox::addRankInput.");
  473. }();
  474. const auto result = container->add(
  475. object_ptr<Ui::InputField>(
  476. container,
  477. st::customBadgeField,
  478. (isOwner ? tr::lng_owner_badge : tr::lng_admin_badge)(),
  479. TextUtilities::RemoveEmoji(_oldRank)),
  480. st::rightsAboutMargin);
  481. result->setMaxLength(kAdminRoleLimit);
  482. result->setInstantReplaces(Ui::InstantReplaces::TextOnly());
  483. result->changes(
  484. ) | rpl::start_with_next([=] {
  485. const auto text = result->getLastText();
  486. const auto removed = TextUtilities::RemoveEmoji(text);
  487. if (removed != text) {
  488. result->setText(removed);
  489. }
  490. }, result->lifetime());
  491. Ui::AddSkip(container);
  492. Ui::AddDividerText(
  493. container,
  494. tr::lng_rights_edit_admin_rank_about(
  495. lt_title,
  496. (isOwner ? tr::lng_owner_badge : tr::lng_admin_badge)()));
  497. Ui::AddSkip(container);
  498. return result;
  499. }
  500. bool EditAdminBox::canTransferOwnership() const {
  501. if (user()->isInaccessible() || user()->isBot() || user()->isSelf()) {
  502. return false;
  503. } else if (const auto chat = peer()->asChat()) {
  504. return chat->amCreator();
  505. } else if (const auto channel = peer()->asChannel()) {
  506. return channel->amCreator();
  507. }
  508. Unexpected("Chat type in EditAdminBox::canTransferOwnership.");
  509. }
  510. not_null<Ui::SlideWrap<Ui::RpWidget>*> EditAdminBox::setupTransferButton(
  511. not_null<Ui::VerticalLayout*> container,
  512. bool isGroup) {
  513. const auto wrap = container->add(
  514. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  515. container,
  516. object_ptr<Ui::VerticalLayout>(container)));
  517. const auto inner = wrap->entity();
  518. inner->add(
  519. object_ptr<Ui::BoxContentDivider>(inner),
  520. { 0, st::infoProfileSkip, 0, st::infoProfileSkip });
  521. inner->add(EditPeerInfoBox::CreateButton(
  522. inner,
  523. (isGroup
  524. ? tr::lng_rights_transfer_group
  525. : tr::lng_rights_transfer_channel)(),
  526. rpl::single(QString()),
  527. [=] { transferOwnership(); },
  528. st::peerPermissionsButton,
  529. {}));
  530. return wrap;
  531. }
  532. void EditAdminBox::transferOwnership() {
  533. if (_checkTransferRequestId) {
  534. return;
  535. }
  536. const auto channel = peer()->isChannel()
  537. ? peer()->asChannel()->inputChannel
  538. : MTP_inputChannelEmpty();
  539. const auto api = &peer()->session().api();
  540. api->cloudPassword().reload();
  541. _checkTransferRequestId = api->request(MTPchannels_EditCreator(
  542. channel,
  543. MTP_inputUserEmpty(),
  544. MTP_inputCheckPasswordEmpty()
  545. )).fail([=](const MTP::Error &error) {
  546. _checkTransferRequestId = 0;
  547. if (!handleTransferPasswordError(error.type())) {
  548. const auto callback = crl::guard(this, [=](Fn<void()> &&close) {
  549. transferOwnershipChecked();
  550. close();
  551. });
  552. getDelegate()->show(Ui::MakeConfirmBox({
  553. .text = tr::lng_rights_transfer_about(
  554. tr::now,
  555. lt_group,
  556. Ui::Text::Bold(peer()->name()),
  557. lt_user,
  558. Ui::Text::Bold(user()->shortName()),
  559. Ui::Text::RichLangValue),
  560. .confirmed = callback,
  561. .confirmText = tr::lng_rights_transfer_sure(),
  562. }));
  563. }
  564. }).send();
  565. }
  566. bool EditAdminBox::handleTransferPasswordError(const QString &error) {
  567. const auto session = &user()->session();
  568. auto about = tr::lng_rights_transfer_check_about(
  569. tr::now,
  570. lt_user,
  571. Ui::Text::Bold(user()->shortName()),
  572. Ui::Text::WithEntities);
  573. if (auto box = PrePasswordErrorBox(error, session, std::move(about))) {
  574. getDelegate()->show(std::move(box));
  575. return true;
  576. }
  577. return false;
  578. }
  579. void EditAdminBox::transferOwnershipChecked() {
  580. if (const auto chat = peer()->asChatNotMigrated()) {
  581. peer()->session().api().migrateChat(chat, crl::guard(this, [=](
  582. not_null<ChannelData*> channel) {
  583. requestTransferPassword(channel);
  584. }));
  585. } else if (const auto channel = peer()->asChannelOrMigrated()) {
  586. requestTransferPassword(channel);
  587. } else {
  588. Unexpected("Peer in SaveAdminCallback.");
  589. }
  590. }
  591. void EditAdminBox::requestTransferPassword(not_null<ChannelData*> channel) {
  592. peer()->session().api().cloudPassword().state(
  593. ) | rpl::take(
  594. 1
  595. ) | rpl::start_with_next([=](const Core::CloudPasswordState &state) {
  596. auto fields = PasscodeBox::CloudFields::From(state);
  597. fields.customTitle = tr::lng_rights_transfer_password_title();
  598. fields.customDescription
  599. = tr::lng_rights_transfer_password_description(tr::now);
  600. fields.customSubmitButton = tr::lng_passcode_submit();
  601. fields.customCheckCallback = crl::guard(this, [=](
  602. const Core::CloudPasswordResult &result,
  603. QPointer<PasscodeBox> box) {
  604. sendTransferRequestFrom(box, channel, result);
  605. });
  606. getDelegate()->show(Box<PasscodeBox>(&channel->session(), fields));
  607. }, lifetime());
  608. }
  609. void EditAdminBox::sendTransferRequestFrom(
  610. QPointer<PasscodeBox> box,
  611. not_null<ChannelData*> channel,
  612. const Core::CloudPasswordResult &result) {
  613. if (_transferRequestId) {
  614. return;
  615. }
  616. const auto weak = Ui::MakeWeak(this);
  617. const auto user = this->user();
  618. const auto api = &channel->session().api();
  619. _transferRequestId = api->request(MTPchannels_EditCreator(
  620. channel->inputChannel,
  621. user->inputUser,
  622. result.result
  623. )).done([=](const MTPUpdates &result) {
  624. api->applyUpdates(result);
  625. if (!box && !weak) {
  626. return;
  627. }
  628. const auto show = box ? box->uiShow() : weak->uiShow();
  629. show->showToast(
  630. (channel->isBroadcast()
  631. ? tr::lng_rights_transfer_done_channel
  632. : tr::lng_rights_transfer_done_group)(
  633. tr::now,
  634. lt_user,
  635. user->shortName()));
  636. show->hideLayer();
  637. }).fail(crl::guard(this, [=](const MTP::Error &error) {
  638. if (weak) {
  639. _transferRequestId = 0;
  640. }
  641. if (box && box->handleCustomCheckError(error)) {
  642. return;
  643. }
  644. const auto &type = error.type();
  645. const auto problem = [&] {
  646. if (type == u"CHANNELS_ADMIN_PUBLIC_TOO_MUCH"_q) {
  647. return tr::lng_channels_too_much_public_other(tr::now);
  648. } else if (type == u"CHANNELS_ADMIN_LOCATED_TOO_MUCH"_q) {
  649. return tr::lng_channels_too_much_located_other(tr::now);
  650. } else if (type == u"ADMINS_TOO_MUCH"_q) {
  651. return (channel->isBroadcast()
  652. ? tr::lng_error_admin_limit_channel
  653. : tr::lng_error_admin_limit)(tr::now);
  654. } else if (type == u"CHANNEL_INVALID"_q) {
  655. return (channel->isBroadcast()
  656. ? tr::lng_channel_not_accessible
  657. : tr::lng_group_not_accessible)(tr::now);
  658. }
  659. return Lang::Hard::ServerError();
  660. }();
  661. const auto recoverable = [&] {
  662. return (type == u"PASSWORD_MISSING"_q)
  663. || type.startsWith(u"PASSWORD_TOO_FRESH_"_q)
  664. || type.startsWith(u"SESSION_TOO_FRESH_"_q);
  665. }();
  666. const auto weak = Ui::MakeWeak(this);
  667. getDelegate()->show(Ui::MakeInformBox(problem));
  668. if (box) {
  669. box->closeBox();
  670. }
  671. if (weak && !recoverable) {
  672. closeBox();
  673. }
  674. })).handleFloodErrors().send();
  675. }
  676. EditRestrictedBox::EditRestrictedBox(
  677. QWidget*,
  678. not_null<PeerData*> peer,
  679. not_null<UserData*> user,
  680. bool hasAdminRights,
  681. ChatRestrictionsInfo rights,
  682. UserData *by,
  683. TimeId since)
  684. : EditParticipantBox(nullptr, peer, user, hasAdminRights)
  685. , _oldRights(rights)
  686. , _by(by)
  687. , _since(since) {
  688. }
  689. void EditRestrictedBox::prepare() {
  690. using Flag = ChatRestriction;
  691. using Flags = ChatRestrictions;
  692. EditParticipantBox::prepare();
  693. setTitle(tr::lng_rights_user_restrictions());
  694. Ui::AddDivider(verticalLayout());
  695. Ui::AddSkip(verticalLayout());
  696. const auto chat = peer()->asChat();
  697. const auto channel = peer()->asChannel();
  698. const auto defaultRestrictions = chat
  699. ? chat->defaultRestrictions()
  700. : channel->defaultRestrictions();
  701. const auto prepareRights = _oldRights.flags
  702. ? _oldRights
  703. : defaultRights();
  704. const auto prepareFlags = FixDependentRestrictions(
  705. prepareRights.flags
  706. | defaultRestrictions
  707. | ((channel && channel->isPublic())
  708. ? (Flag::ChangeInfo | Flag::PinMessages)
  709. : Flags(0)));
  710. const auto disabledMessages = [&] {
  711. auto result = base::flat_map<Flags, QString>();
  712. if (!canSave()) {
  713. result.emplace(
  714. ~Flags(0),
  715. tr::lng_rights_about_restriction_cant_edit(tr::now));
  716. } else {
  717. const auto disabled = FixDependentRestrictions(
  718. defaultRestrictions
  719. | ((channel && channel->isPublic())
  720. ? (Flag::ChangeInfo | Flag::PinMessages)
  721. : Flags(0)));
  722. result.emplace(
  723. disabled,
  724. tr::lng_rights_restriction_for_all(tr::now));
  725. }
  726. return result;
  727. }();
  728. Ui::AddSubsectionTitle(
  729. verticalLayout(),
  730. tr::lng_rights_user_restrictions_header());
  731. auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
  732. this,
  733. prepareFlags,
  734. disabledMessages,
  735. { .isForum = peer()->isForum() });
  736. addControl(std::move(checkboxes), QMargins());
  737. _until = prepareRights.until;
  738. addControl(
  739. object_ptr<Ui::FixedHeightWidget>(this, st::defaultVerticalListSkip));
  740. Ui::AddDivider(verticalLayout());
  741. addControl(
  742. object_ptr<Ui::FlatLabel>(
  743. this,
  744. tr::lng_rights_chat_banned_until_header(tr::now),
  745. st::rightsHeaderLabel),
  746. st::rightsHeaderMargin);
  747. setRestrictUntil(_until);
  748. //addControl(
  749. // object_ptr<Ui::LinkButton>(
  750. // this,
  751. // tr::lng_rights_chat_banned_block(tr::now),
  752. // st::boxLinkButton));
  753. if (_since) {
  754. const auto parsed = base::unixtime::parse(_since);
  755. const auto inner = addControl(object_ptr<Ui::VerticalLayout>(this));
  756. const auto isBanned = (_oldRights.flags
  757. & ChatRestriction::ViewMessages);
  758. Ui::AddSkip(inner);
  759. const auto label = Ui::AddDividerText(
  760. inner,
  761. (isBanned
  762. ? tr::lng_rights_chat_banned_by
  763. : tr::lng_rights_chat_restricted_by)(
  764. lt_user,
  765. rpl::single(_by
  766. ? Ui::Text::Link(_by->name(), 1)
  767. : TextWithEntities{ QString::fromUtf8("\U0001F47B") }),
  768. lt_date,
  769. rpl::single(TextWithEntities{ langDateTimeFull(parsed) }),
  770. Ui::Text::WithEntities));
  771. if (_by) {
  772. label->setLink(1, _by->createOpenLink());
  773. }
  774. }
  775. if (canSave()) {
  776. const auto save = [=, value = getRestrictions] {
  777. if (!_saveCallback) {
  778. return;
  779. }
  780. _saveCallback(
  781. _oldRights,
  782. ChatRestrictionsInfo{ value(), getRealUntilValue() });
  783. };
  784. addButton(tr::lng_settings_save(), save);
  785. addButton(tr::lng_cancel(), [=] { closeBox(); });
  786. } else {
  787. addButton(tr::lng_box_ok(), [=] { closeBox(); });
  788. }
  789. }
  790. ChatRestrictionsInfo EditRestrictedBox::defaultRights() const {
  791. return ChatRestrictionsInfo();
  792. }
  793. void EditRestrictedBox::showRestrictUntil() {
  794. uiShow()->showBox(Box([=](not_null<Ui::GenericBox*> box) {
  795. const auto save = [=](TimeId result) {
  796. if (!result) {
  797. return;
  798. }
  799. setRestrictUntil(result);
  800. box->closeBox();
  801. };
  802. const auto now = base::unixtime::now();
  803. const auto time = isUntilForever()
  804. ? (now + kSecondsInDay)
  805. : getRealUntilValue();
  806. ChooseDateTimeBox(box, {
  807. .title = tr::lng_rights_chat_banned_until_header(),
  808. .submit = tr::lng_settings_save(),
  809. .done = save,
  810. .min = [=] { return now; },
  811. .time = time,
  812. .max = [=] {
  813. return now + kSecondsInDay * kMaxRestrictDelayDays;
  814. },
  815. });
  816. }));
  817. }
  818. void EditRestrictedBox::setRestrictUntil(TimeId until) {
  819. _until = until;
  820. _untilVariants.clear();
  821. createUntilGroup();
  822. createUntilVariants();
  823. }
  824. bool EditRestrictedBox::isUntilForever() const {
  825. return ChannelData::IsRestrictedForever(_until);
  826. }
  827. void EditRestrictedBox::createUntilGroup() {
  828. _untilGroup = std::make_shared<Ui::RadiobuttonGroup>(
  829. isUntilForever() ? 0 : _until);
  830. _untilGroup->setChangedCallback([this](int value) {
  831. if (value == kUntilCustom) {
  832. _untilGroup->setValue(_until);
  833. showRestrictUntil();
  834. } else if (_until != value) {
  835. _until = value;
  836. }
  837. });
  838. }
  839. void EditRestrictedBox::createUntilVariants() {
  840. auto addVariant = [&](int value, const QString &text) {
  841. if (!canSave() && _untilGroup->current() != value) {
  842. return;
  843. }
  844. _untilVariants.emplace_back(
  845. addControl(
  846. object_ptr<Ui::Radiobutton>(
  847. this,
  848. _untilGroup,
  849. value,
  850. text,
  851. st::defaultCheckbox),
  852. st::rightsToggleMargin));
  853. if (!canSave()) {
  854. _untilVariants.back()->setDisabled(true);
  855. }
  856. };
  857. auto addCustomVariant = [&](TimeId until, TimeId from, TimeId to) {
  858. if (!ChannelData::IsRestrictedForever(until)
  859. && until > from
  860. && until <= to) {
  861. addVariant(
  862. until,
  863. tr::lng_rights_chat_banned_custom_date(
  864. tr::now,
  865. lt_date,
  866. langDateTime(base::unixtime::parse(until))));
  867. }
  868. };
  869. auto addCurrentVariant = [&](TimeId from, TimeId to) {
  870. auto oldUntil = _oldRights.until;
  871. if (oldUntil < _until) {
  872. addCustomVariant(oldUntil, from, to);
  873. }
  874. addCustomVariant(_until, from, to);
  875. if (oldUntil > _until) {
  876. addCustomVariant(oldUntil, from, to);
  877. }
  878. };
  879. addVariant(0, tr::lng_rights_chat_banned_forever(tr::now));
  880. auto now = base::unixtime::now();
  881. auto nextDay = now + kSecondsInDay;
  882. auto nextWeek = now + kSecondsInWeek;
  883. addCurrentVariant(0, nextDay);
  884. addVariant(kUntilOneDay, tr::lng_rights_chat_banned_day(tr::now, lt_count, 1));
  885. addCurrentVariant(nextDay, nextWeek);
  886. addVariant(kUntilOneWeek, tr::lng_rights_chat_banned_week(tr::now, lt_count, 1));
  887. addCurrentVariant(nextWeek, INT_MAX);
  888. addVariant(kUntilCustom, tr::lng_rights_chat_banned_custom(tr::now));
  889. }
  890. TimeId EditRestrictedBox::getRealUntilValue() const {
  891. Expects(_until != kUntilCustom);
  892. if (_until == kUntilOneDay) {
  893. return base::unixtime::now() + kSecondsInDay;
  894. } else if (_until == kUntilOneWeek) {
  895. return base::unixtime::now() + kSecondsInWeek;
  896. }
  897. Assert(_until >= 0);
  898. return _until;
  899. }