edit_peer_permissions_box.cpp 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490
  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_peer_permissions_box.h"
  8. #include "lang/lang_keys.h"
  9. #include "history/admin_log/history_admin_log_filter.h"
  10. #include "core/ui_integration.h"
  11. #include "data/stickers/data_custom_emoji.h"
  12. #include "data/data_channel.h"
  13. #include "data/data_chat.h"
  14. #include "data/data_session.h"
  15. #include "ui/effects/toggle_arrow.h"
  16. #include "ui/wrap/slide_wrap.h"
  17. #include "ui/wrap/vertical_layout.h"
  18. #include "ui/layers/generic_box.h"
  19. #include "ui/painter.h"
  20. #include "ui/vertical_list.h"
  21. #include "ui/widgets/labels.h"
  22. #include "ui/widgets/checkbox.h"
  23. #include "ui/widgets/buttons.h"
  24. #include "ui/widgets/continuous_sliders.h"
  25. #include "ui/widgets/box_content_divider.h"
  26. #include "ui/text/text_utilities.h"
  27. #include "ui/toast/toast.h"
  28. #include "info/profile/info_profile_icon.h"
  29. #include "info/profile/info_profile_values.h"
  30. #include "boxes/peers/edit_participants_box.h"
  31. #include "boxes/peers/edit_peer_info_box.h"
  32. #include "boxes/edit_privacy_box.h"
  33. #include "settings/settings_power_saving.h"
  34. #include "window/window_session_controller.h"
  35. #include "window/window_controller.h"
  36. #include "main/main_session.h"
  37. #include "mtproto/mtproto_config.h" // megagroupSizeMax
  38. #include "apiwrap.h"
  39. #include "settings/settings_common.h"
  40. #include "styles/style_layers.h"
  41. #include "styles/style_boxes.h"
  42. #include "styles/style_chat.h"
  43. #include "styles/style_info.h"
  44. #include "styles/style_menu_icons.h"
  45. #include "styles/style_window.h"
  46. #include "styles/style_settings.h"
  47. namespace {
  48. constexpr auto kSlowmodeValues = 7;
  49. constexpr auto kBoostsUnrestrictValues = 5;
  50. constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
  51. [[nodiscard]] auto Dependencies(PowerSaving::Flags)
  52. -> std::vector<std::pair<PowerSaving::Flag, PowerSaving::Flag>> {
  53. return {};
  54. }
  55. [[nodiscard]] auto Dependencies(AdminLog::FilterValue::Flags) {
  56. using Flag = AdminLog::FilterValue::Flag;
  57. return std::vector<std::pair<Flag, Flag>>{};
  58. }
  59. [[nodiscard]] auto NestedRestrictionLabelsList(
  60. Data::RestrictionsSetOptions options)
  61. -> std::vector<NestedEditFlagsLabels<ChatRestrictions>> {
  62. using Flag = ChatRestriction;
  63. auto first = std::vector<RestrictionLabel>{
  64. { Flag::SendOther, tr::lng_rights_chat_send_text(tr::now) },
  65. };
  66. auto media = std::vector<RestrictionLabel>{
  67. { Flag::SendPhotos, tr::lng_rights_chat_photos(tr::now) },
  68. { Flag::SendVideos, tr::lng_rights_chat_videos(tr::now) },
  69. { Flag::SendVideoMessages, tr::lng_rights_chat_video_messages(tr::now) },
  70. { Flag::SendMusic, tr::lng_rights_chat_music(tr::now) },
  71. { Flag::SendVoiceMessages, tr::lng_rights_chat_voice_messages(tr::now) },
  72. { Flag::SendFiles, tr::lng_rights_chat_files(tr::now) },
  73. { Flag::SendStickers
  74. | Flag::SendGifs
  75. | Flag::SendGames
  76. | Flag::SendInline, tr::lng_rights_chat_stickers(tr::now) },
  77. { Flag::EmbedLinks, tr::lng_rights_chat_send_links(tr::now) },
  78. { Flag::SendPolls, tr::lng_rights_chat_send_polls(tr::now) },
  79. };
  80. auto second = std::vector<RestrictionLabel>{
  81. { Flag::AddParticipants, tr::lng_rights_chat_add_members(tr::now) },
  82. { Flag::CreateTopics, tr::lng_rights_group_add_topics(tr::now) },
  83. { Flag::PinMessages, tr::lng_rights_group_pin(tr::now) },
  84. { Flag::ChangeInfo, tr::lng_rights_group_info(tr::now) },
  85. };
  86. if (!options.isForum) {
  87. second.erase(
  88. ranges::remove(
  89. second,
  90. Flag::CreateTopics | Flag(),
  91. &RestrictionLabel::flags),
  92. end(second));
  93. }
  94. return {
  95. { std::nullopt, std::move(first) },
  96. { tr::lng_rights_chat_send_media(), std::move(media) },
  97. { std::nullopt, std::move(second) },
  98. };
  99. }
  100. [[nodiscard]] auto NestedAdminRightLabels(
  101. Data::AdminRightsSetOptions options)
  102. -> std::vector<NestedEditFlagsLabels<ChatAdminRights>> {
  103. using Flag = ChatAdminRight;
  104. if (options.isGroup) {
  105. auto first = std::vector<AdminRightLabel>{
  106. { Flag::ChangeInfo, tr::lng_rights_group_info(tr::now) },
  107. { Flag::DeleteMessages, tr::lng_rights_group_delete(tr::now) },
  108. { Flag::BanUsers, tr::lng_rights_group_ban(tr::now) },
  109. { Flag::InviteByLinkOrAdd, options.anyoneCanAddMembers
  110. ? tr::lng_rights_group_invite_link(tr::now)
  111. : tr::lng_rights_group_invite(tr::now) },
  112. { Flag::ManageTopics, tr::lng_rights_group_topics(tr::now) },
  113. { Flag::PinMessages, tr::lng_rights_group_pin(tr::now) },
  114. };
  115. auto stories = std::vector<AdminRightLabel>{
  116. { Flag::PostStories, tr::lng_rights_channel_post_stories(tr::now) },
  117. { Flag::EditStories, tr::lng_rights_channel_edit_stories(tr::now) },
  118. { Flag::DeleteStories, tr::lng_rights_channel_delete_stories(tr::now) },
  119. };
  120. auto second = std::vector<AdminRightLabel>{
  121. { Flag::ManageCall, tr::lng_rights_group_manage_calls(tr::now) },
  122. { Flag::Anonymous, tr::lng_rights_group_anonymous(tr::now) },
  123. { Flag::AddAdmins, tr::lng_rights_add_admins(tr::now) },
  124. };
  125. if (!options.isForum) {
  126. first.erase(
  127. ranges::remove(
  128. first,
  129. Flag::ManageTopics | Flag(),
  130. &AdminRightLabel::flags),
  131. end(first));
  132. }
  133. return {
  134. { std::nullopt, std::move(first) },
  135. { tr::lng_rights_channel_manage_stories(), std::move(stories) },
  136. { std::nullopt, std::move(second) },
  137. };
  138. }
  139. auto first = std::vector<AdminRightLabel>{
  140. { Flag::ChangeInfo, tr::lng_rights_channel_info(tr::now) },
  141. };
  142. auto messages = std::vector<AdminRightLabel>{
  143. { Flag::PostMessages, tr::lng_rights_channel_post(tr::now) },
  144. { Flag::EditMessages, tr::lng_rights_channel_edit(tr::now) },
  145. { Flag::DeleteMessages, tr::lng_rights_channel_delete(tr::now) },
  146. };
  147. auto stories = std::vector<AdminRightLabel>{
  148. { Flag::PostStories, tr::lng_rights_channel_post_stories(tr::now) },
  149. { Flag::EditStories, tr::lng_rights_channel_edit_stories(tr::now) },
  150. { Flag::DeleteStories, tr::lng_rights_channel_delete_stories(tr::now) },
  151. };
  152. auto second = std::vector<AdminRightLabel>{
  153. { Flag::InviteByLinkOrAdd, tr::lng_rights_group_invite(tr::now) },
  154. { Flag::ManageCall, tr::lng_rights_channel_manage_calls(tr::now) },
  155. { Flag::AddAdmins, tr::lng_rights_add_admins(tr::now) },
  156. };
  157. return {
  158. { std::nullopt, std::move(first) },
  159. { tr::lng_rights_channel_manage(), std::move(messages) },
  160. { tr::lng_rights_channel_manage_stories(), std::move(stories) },
  161. { std::nullopt, std::move(second) },
  162. };
  163. }
  164. int SlowmodeDelayByIndex(int index) {
  165. Expects(index >= 0 && index < kSlowmodeValues);
  166. switch (index) {
  167. case 0: return 0;
  168. case 1: return 10;
  169. case 2: return 30;
  170. case 3: return 60;
  171. case 4: return 5 * 60;
  172. case 5: return 15 * 60;
  173. case 6: return 60 * 60;
  174. }
  175. Unexpected("Index in SlowmodeDelayByIndex.");
  176. }
  177. [[nodiscard]] int BoostsUnrestrictByIndex(int index) {
  178. return index + 1;
  179. }
  180. template <typename CheckboxesMap, typename DependenciesMap>
  181. void ApplyDependencies(
  182. const CheckboxesMap &checkboxes,
  183. const DependenciesMap &dependencies,
  184. Ui::AbstractCheckView *changed) {
  185. const auto checkAndApply = [&](
  186. auto &&current,
  187. auto dependency,
  188. bool isChecked) {
  189. for (auto &&checkbox : checkboxes) {
  190. if ((checkbox.first & dependency)
  191. && (checkbox.second->checked() == isChecked)) {
  192. current->setChecked(isChecked, anim::type::normal);
  193. return true;
  194. }
  195. }
  196. return false;
  197. };
  198. const auto applySomeDependency = [&] {
  199. auto result = false;
  200. for (auto &&entry : checkboxes) {
  201. if (entry.second == changed) {
  202. continue;
  203. }
  204. auto isChecked = entry.second->checked();
  205. for (auto &&dependency : dependencies) {
  206. const auto check = isChecked
  207. ? dependency.first
  208. : dependency.second;
  209. if (entry.first & check) {
  210. if (checkAndApply(
  211. entry.second,
  212. (isChecked
  213. ? dependency.second
  214. : dependency.first),
  215. !isChecked)) {
  216. result = true;
  217. break;
  218. }
  219. }
  220. }
  221. }
  222. return result;
  223. };
  224. const auto maxFixesCount = int(checkboxes.size());
  225. for (auto i = 0; i != maxFixesCount; ++i) {
  226. if (!applySomeDependency()) {
  227. break;
  228. }
  229. };
  230. }
  231. auto Dependencies(ChatRestrictions)
  232. -> std::vector<std::pair<ChatRestriction, ChatRestriction>> {
  233. using Flag = ChatRestriction;
  234. return {
  235. // stickers <-> gifs
  236. { Flag::SendGifs, Flag::SendStickers },
  237. { Flag::SendStickers, Flag::SendGifs },
  238. // stickers <-> games
  239. { Flag::SendGames, Flag::SendStickers },
  240. { Flag::SendStickers, Flag::SendGames },
  241. // stickers <-> inline
  242. { Flag::SendInline, Flag::SendStickers },
  243. { Flag::SendStickers, Flag::SendInline },
  244. // embed_links -> send_plain
  245. { Flag::EmbedLinks, Flag::SendOther },
  246. // send_* -> view_messages
  247. { Flag::SendStickers, Flag::ViewMessages },
  248. { Flag::SendGifs, Flag::ViewMessages },
  249. { Flag::SendGames, Flag::ViewMessages },
  250. { Flag::SendInline, Flag::ViewMessages },
  251. { Flag::SendPolls, Flag::ViewMessages },
  252. { Flag::SendPhotos, Flag::ViewMessages },
  253. { Flag::SendVideos, Flag::ViewMessages },
  254. { Flag::SendVideoMessages, Flag::ViewMessages },
  255. { Flag::SendMusic, Flag::ViewMessages },
  256. { Flag::SendVoiceMessages, Flag::ViewMessages },
  257. { Flag::SendFiles, Flag::ViewMessages },
  258. { Flag::SendOther, Flag::ViewMessages },
  259. };
  260. }
  261. ChatRestrictions NegateRestrictions(ChatRestrictions value) {
  262. using Flag = ChatRestriction;
  263. return (~value) & (Flag(0)
  264. // view_messages is always allowed, so it is never in restrictions.
  265. //| Flag::ViewMessages
  266. | Flag::ChangeInfo
  267. | Flag::EmbedLinks
  268. | Flag::AddParticipants
  269. | Flag::CreateTopics
  270. | Flag::PinMessages
  271. | Flag::SendGames
  272. | Flag::SendGifs
  273. | Flag::SendInline
  274. | Flag::SendPolls
  275. | Flag::SendStickers
  276. | Flag::SendPhotos
  277. | Flag::SendVideos
  278. | Flag::SendVideoMessages
  279. | Flag::SendMusic
  280. | Flag::SendVoiceMessages
  281. | Flag::SendFiles
  282. | Flag::SendOther);
  283. }
  284. auto Dependencies(ChatAdminRights)
  285. -> std::vector<std::pair<ChatAdminRight, ChatAdminRight>> {
  286. return {};
  287. }
  288. auto ToPositiveNumberString() {
  289. return rpl::map([](int count) {
  290. return count ? QString::number(count) : QString();
  291. });
  292. }
  293. ChatRestrictions DisabledByAdminRights(not_null<PeerData*> peer) {
  294. using Flag = ChatRestriction;
  295. using Admin = ChatAdminRight;
  296. using Admins = ChatAdminRights;
  297. const auto adminRights = [&] {
  298. const auto full = ~Admins(0);
  299. if (const auto chat = peer->asChat()) {
  300. return chat->amCreator() ? full : chat->adminRights();
  301. } else if (const auto channel = peer->asChannel()) {
  302. return channel->amCreator() ? full : channel->adminRights();
  303. }
  304. Unexpected("User in DisabledByAdminRights.");
  305. }();
  306. return Flag(0)
  307. | ((adminRights & Admin::ManageTopics)
  308. ? Flag(0)
  309. : Flag::CreateTopics)
  310. | ((adminRights & Admin::PinMessages)
  311. ? Flag(0)
  312. : Flag::PinMessages)
  313. | ((adminRights & Admin::InviteByLinkOrAdd)
  314. ? Flag(0)
  315. : Flag::AddParticipants)
  316. | ((adminRights & Admin::ChangeInfo)
  317. ? Flag(0)
  318. : Flag::ChangeInfo);
  319. }
  320. not_null<Ui::RpWidget*> AddInnerToggle(
  321. not_null<Ui::VerticalLayout*> container,
  322. const style::SettingsButton &st,
  323. std::vector<not_null<Ui::AbstractCheckView*>> innerCheckViews,
  324. not_null<Ui::SlideWrap<>*> wrap,
  325. rpl::producer<QString> buttonLabel,
  326. std::optional<QString> locked,
  327. Settings::IconDescriptor &&icon) {
  328. const auto button = container->add(object_ptr<Ui::SettingsButton>(
  329. container,
  330. nullptr,
  331. st));
  332. if (icon) {
  333. Settings::AddButtonIcon(button, st, std::move(icon));
  334. }
  335. const auto toggleButton = Ui::CreateChild<Ui::SettingsButton>(
  336. container.get(),
  337. nullptr,
  338. st);
  339. struct State final {
  340. State(const style::Toggle &st, Fn<void()> c)
  341. : checkView(st, false, c) {
  342. }
  343. Ui::ToggleView checkView;
  344. Ui::Animations::Simple animation;
  345. rpl::event_stream<> anyChanges;
  346. std::vector<not_null<Ui::AbstractCheckView*>> innerChecks;
  347. };
  348. const auto state = button->lifetime().make_state<State>(
  349. st.toggle,
  350. [=] { toggleButton->update(); });
  351. state->innerChecks = std::move(innerCheckViews);
  352. const auto countChecked = [=] {
  353. return ranges::count_if(
  354. state->innerChecks,
  355. [](const auto &v) { return v->checked(); });
  356. };
  357. for (const auto &innerCheck : state->innerChecks) {
  358. innerCheck->checkedChanges(
  359. ) | rpl::to_empty | rpl::start_to_stream(
  360. state->anyChanges,
  361. button->lifetime());
  362. }
  363. const auto checkView = &state->checkView;
  364. {
  365. const auto separator = Ui::CreateChild<Ui::RpWidget>(container.get());
  366. separator->paintRequest(
  367. ) | rpl::start_with_next([=, bg = st.textBgOver] {
  368. auto p = QPainter(separator);
  369. p.fillRect(separator->rect(), bg);
  370. }, separator->lifetime());
  371. const auto separatorHeight = 2 * st.toggle.border
  372. + st.toggle.diameter;
  373. button->geometryValue(
  374. ) | rpl::start_with_next([=](const QRect &r) {
  375. const auto w = st::rightsButtonToggleWidth;
  376. toggleButton->setGeometry(
  377. r.x() + r.width() - w,
  378. r.y(),
  379. w,
  380. r.height());
  381. separator->setGeometry(
  382. toggleButton->x() - st::lineWidth,
  383. r.y() + (r.height() - separatorHeight) / 2,
  384. st::lineWidth,
  385. separatorHeight);
  386. }, toggleButton->lifetime());
  387. const auto checkWidget = Ui::CreateChild<Ui::RpWidget>(toggleButton);
  388. checkWidget->resize(checkView->getSize());
  389. checkWidget->paintRequest(
  390. ) | rpl::start_with_next([=] {
  391. auto p = QPainter(checkWidget);
  392. checkView->paint(p, 0, 0, checkWidget->width());
  393. }, checkWidget->lifetime());
  394. toggleButton->sizeValue(
  395. ) | rpl::start_with_next([=](const QSize &s) {
  396. checkWidget->moveToRight(
  397. st.toggleSkip,
  398. (s.height() - checkWidget->height()) / 2);
  399. }, toggleButton->lifetime());
  400. }
  401. state->anyChanges.events_starting_with(
  402. rpl::empty_value()
  403. ) | rpl::map(countChecked) | rpl::start_with_next([=](int count) {
  404. checkView->setChecked(count > 0, anim::type::normal);
  405. }, toggleButton->lifetime());
  406. checkView->setLocked(locked.has_value());
  407. checkView->finishAnimating();
  408. const auto totalInnerChecks = state->innerChecks.size();
  409. const auto label = Ui::CreateChild<Ui::FlatLabel>(
  410. button,
  411. rpl::combine(
  412. std::move(buttonLabel),
  413. state->anyChanges.events_starting_with(
  414. rpl::empty_value()
  415. ) | rpl::map(countChecked)
  416. ) | rpl::map([=](const QString &t, int checked) {
  417. auto count = Ui::Text::Bold(" "
  418. + QString::number(checked)
  419. + '/'
  420. + QString::number(totalInnerChecks));
  421. return TextWithEntities::Simple(t).append(std::move(count));
  422. }));
  423. label->setAttribute(Qt::WA_TransparentForMouseEvents);
  424. const auto arrow = Ui::CreateChild<Ui::RpWidget>(button);
  425. {
  426. const auto &icon = st::permissionsExpandIcon;
  427. arrow->resize(icon.size());
  428. arrow->paintRequest(
  429. ) | rpl::start_with_next([=, &icon] {
  430. auto p = QPainter(arrow);
  431. const auto center = QPointF(
  432. icon.width() / 2.,
  433. icon.height() / 2.);
  434. const auto progress = state->animation.value(
  435. wrap->toggled() ? 1. : 0.);
  436. auto hq = std::optional<PainterHighQualityEnabler>();
  437. if (progress > 0.) {
  438. hq.emplace(p);
  439. p.translate(center);
  440. p.rotate(progress * 180.);
  441. p.translate(-center);
  442. }
  443. icon.paint(p, 0, 0, arrow->width());
  444. }, arrow->lifetime());
  445. }
  446. button->sizeValue(
  447. ) | rpl::start_with_next([=, &st](const QSize &s) {
  448. const auto labelLeft = st.padding.left();
  449. const auto labelRight = s.width() - toggleButton->width();
  450. label->resizeToWidth(labelRight - labelLeft - arrow->width());
  451. label->moveToLeft(
  452. labelLeft,
  453. (s.height() - label->height()) / 2);
  454. arrow->moveToLeft(
  455. std::min(
  456. labelLeft + label->textMaxWidth(),
  457. labelRight - arrow->width()),
  458. (s.height() - arrow->height()) / 2);
  459. }, button->lifetime());
  460. wrap->toggledValue(
  461. ) | rpl::skip(1) | rpl::start_with_next([=](bool toggled) {
  462. state->animation.start(
  463. [=] { arrow->update(); },
  464. toggled ? 0. : 1.,
  465. toggled ? 1. : 0.,
  466. st::slideWrapDuration);
  467. }, button->lifetime());
  468. const auto handleLocked = [=] {
  469. if (locked.has_value()) {
  470. Ui::Toast::Show(container, *locked);
  471. return true;
  472. }
  473. return false;
  474. };
  475. button->clicks(
  476. ) | rpl::start_with_next([=] {
  477. if (!handleLocked()) {
  478. wrap->toggle(!wrap->toggled(), anim::type::normal);
  479. }
  480. }, button->lifetime());
  481. toggleButton->clicks(
  482. ) | rpl::start_with_next([=] {
  483. if (!handleLocked()) {
  484. const auto checked = !checkView->checked();
  485. for (const auto &innerCheck : state->innerChecks) {
  486. innerCheck->setChecked(checked, anim::type::normal);
  487. }
  488. }
  489. }, toggleButton->lifetime());
  490. return button;
  491. }
  492. template <typename Flags>
  493. [[nodiscard]] EditFlagsControl<Flags> CreateEditFlags(
  494. not_null<Ui::VerticalLayout*> container,
  495. Flags checked,
  496. EditFlagsDescriptor<Flags> &&descriptor) {
  497. struct State final {
  498. std::map<Flags, not_null<Ui::AbstractCheckView*>> checkViews;
  499. rpl::event_stream<> anyChanges;
  500. rpl::variable<QString> forceDisabledMessage;
  501. rpl::variable<bool> forceDisabled;
  502. base::flat_map<Flags, bool> realCheckedValues;
  503. base::weak_ptr<Ui::Toast::Instance> toast;
  504. };
  505. const auto state = container->lifetime().make_state<State>();
  506. if (descriptor.forceDisabledMessage) {
  507. state->forceDisabledMessage = std::move(
  508. descriptor.forceDisabledMessage);
  509. state->forceDisabled = state->forceDisabledMessage.value(
  510. ) | rpl::map([=](const QString &message) {
  511. return !message.isEmpty();
  512. });
  513. state->forceDisabled.value(
  514. ) | rpl::start_with_next([=](bool disabled) {
  515. if (disabled) {
  516. for (const auto &[flags, checkView] : state->checkViews) {
  517. checkView->setChecked(false, anim::type::normal);
  518. }
  519. } else {
  520. for (const auto &[flags, checkView] : state->checkViews) {
  521. if (const auto i = state->realCheckedValues.find(flags)
  522. ; i != state->realCheckedValues.end()) {
  523. checkView->setChecked(
  524. i->second,
  525. anim::type::normal);
  526. }
  527. }
  528. }
  529. }, container->lifetime());
  530. }
  531. const auto &st = descriptor.st ? *descriptor.st : st::rightsButton;
  532. const auto value = [=] {
  533. auto result = Flags(0);
  534. for (const auto &[flags, checkView] : state->checkViews) {
  535. if (checkView->checked()) {
  536. result |= flags;
  537. } else {
  538. result &= ~flags;
  539. }
  540. }
  541. return result;
  542. };
  543. const auto applyDependencies = [=](Ui::AbstractCheckView *view) {
  544. static const auto dependencies = Dependencies(Flags());
  545. ApplyDependencies(state->checkViews, dependencies, view);
  546. };
  547. const auto addCheckbox = [&](
  548. not_null<Ui::VerticalLayout*> verticalLayout,
  549. bool isInner,
  550. const EditFlagsLabel<Flags> &entry) {
  551. const auto flags = entry.flags;
  552. const auto lockedIt = ranges::find_if(
  553. descriptor.disabledMessages,
  554. [&](const auto &pair) { return (pair.first & flags) != 0; });
  555. const auto locked = (lockedIt != end(descriptor.disabledMessages))
  556. ? std::make_optional(lockedIt->second)
  557. : std::nullopt;
  558. const auto realChecked = (checked & flags) != 0;
  559. state->realCheckedValues.emplace(flags, realChecked);
  560. const auto toggled = realChecked && !state->forceDisabled.current();
  561. const auto checkView = [&]() -> not_null<Ui::AbstractCheckView*> {
  562. if (isInner) {
  563. const auto checkbox = verticalLayout->add(
  564. object_ptr<Ui::Checkbox>(
  565. verticalLayout,
  566. entry.label,
  567. toggled,
  568. st::settingsCheckbox),
  569. st.padding);
  570. const auto button = Ui::CreateChild<Ui::RippleButton>(
  571. verticalLayout.get(),
  572. st::defaultRippleAnimation);
  573. button->stackUnder(checkbox);
  574. rpl::combine(
  575. verticalLayout->widthValue(),
  576. checkbox->geometryValue()
  577. ) | rpl::start_with_next([=](int w, const QRect &r) {
  578. button->setGeometry(0, r.y(), w, r.height());
  579. }, button->lifetime());
  580. checkbox->setAttribute(Qt::WA_TransparentForMouseEvents);
  581. const auto checkView = checkbox->checkView();
  582. button->setClickedCallback([=] {
  583. checkView->setChecked(
  584. !checkView->checked(),
  585. anim::type::normal);
  586. });
  587. return checkView;
  588. } else {
  589. const auto button = Settings::AddButtonWithIcon(
  590. verticalLayout,
  591. rpl::single(entry.label),
  592. st,
  593. { entry.icon });
  594. const auto toggle = Ui::CreateChild<Ui::RpWidget>(
  595. button.get());
  596. // Looks like a bug in Clang, fails to compile with 'auto&' below.
  597. rpl::lifetime &lifetime = toggle->lifetime();
  598. const auto checkView = lifetime.make_state<Ui::ToggleView>(
  599. st.toggle,
  600. toggled,
  601. [=] { toggle->update(); });
  602. toggle->resize(checkView->getSize());
  603. toggle->paintRequest(
  604. ) | rpl::start_with_next([=] {
  605. auto p = QPainter(toggle);
  606. checkView->paint(p, 0, 0, toggle->width());
  607. }, toggle->lifetime());
  608. button->sizeValue(
  609. ) | rpl::start_with_next([=](const QSize &s) {
  610. toggle->moveToRight(
  611. st.toggleSkip,
  612. (s.height() - toggle->height()) / 2);
  613. }, toggle->lifetime());
  614. button->setClickedCallback([=] {
  615. checkView->setChecked(
  616. !checkView->checked(),
  617. anim::type::normal);
  618. });
  619. checkView->setLocked(locked.has_value());
  620. return checkView;
  621. }
  622. }();
  623. state->checkViews.emplace(flags, checkView);
  624. checkView->checkedChanges(
  625. ) | rpl::start_with_next([=](bool checked) {
  626. if (checked && state->forceDisabled.current()) {
  627. if (!state->toast) {
  628. state->toast = Ui::Toast::Show(container, {
  629. .text = { state->forceDisabledMessage.current() },
  630. .duration = kForceDisableTooltipDuration,
  631. });
  632. }
  633. checkView->setChecked(false, anim::type::instant);
  634. } else if (locked.has_value()) {
  635. if (checked != toggled) {
  636. if (!state->toast) {
  637. state->toast = Ui::Toast::Show(container, {
  638. .text = { *locked },
  639. .duration = kForceDisableTooltipDuration,
  640. });
  641. }
  642. checkView->setChecked(toggled, anim::type::instant);
  643. }
  644. } else {
  645. if (!state->forceDisabled.current()) {
  646. state->realCheckedValues[flags] = checked;
  647. }
  648. InvokeQueued(container, [=] {
  649. applyDependencies(checkView);
  650. state->anyChanges.fire({});
  651. });
  652. }
  653. }, verticalLayout->lifetime());
  654. return checkView;
  655. };
  656. for (const auto &nestedWithLabel : descriptor.labels) {
  657. Assert(!nestedWithLabel.nested.empty());
  658. const auto isInner = nestedWithLabel.nestingLabel.has_value();
  659. auto wrap = isInner
  660. ? object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  661. container,
  662. object_ptr<Ui::VerticalLayout>(container))
  663. : object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>{ nullptr };
  664. const auto verticalLayout = wrap ? wrap->entity() : container.get();
  665. auto innerChecks = std::vector<not_null<Ui::AbstractCheckView*>>();
  666. for (const auto &entry : nestedWithLabel.nested) {
  667. const auto c = addCheckbox(verticalLayout, isInner, entry);
  668. if (isInner) {
  669. innerChecks.push_back(c);
  670. }
  671. }
  672. if (wrap) {
  673. const auto raw = wrap.data();
  674. raw->hide(anim::type::instant);
  675. AddInnerToggle(
  676. container,
  677. st,
  678. innerChecks,
  679. raw,
  680. *nestedWithLabel.nestingLabel,
  681. std::nullopt,
  682. { nestedWithLabel.nested.front().icon });
  683. container->add(std::move(wrap));
  684. container->widthValue(
  685. ) | rpl::start_with_next([=](int w) {
  686. raw->resizeToWidth(w);
  687. }, raw->lifetime());
  688. }
  689. }
  690. applyDependencies(nullptr);
  691. for (const auto &[flags, checkView] : state->checkViews) {
  692. checkView->finishAnimating();
  693. }
  694. return {
  695. nullptr,
  696. value,
  697. state->anyChanges.events() | rpl::map(value)
  698. };
  699. }
  700. void AddSlowmodeLabels(
  701. not_null<Ui::VerticalLayout*> container) {
  702. const auto labels = container->add(
  703. object_ptr<Ui::FixedHeightWidget>(container, st::normalFont->height),
  704. st::slowmodeLabelsMargin);
  705. for (auto i = 0; i != kSlowmodeValues; ++i) {
  706. const auto seconds = SlowmodeDelayByIndex(i);
  707. const auto label = Ui::CreateChild<Ui::LabelSimple>(
  708. labels,
  709. st::slowmodeLabel,
  710. (!seconds
  711. ? tr::lng_rights_slowmode_off(tr::now)
  712. : (seconds < 60)
  713. ? tr::lng_seconds_tiny(tr::now, lt_count, seconds)
  714. : (seconds < 3600)
  715. ? tr::lng_minutes_tiny(tr::now, lt_count, seconds / 60)
  716. : tr::lng_hours_tiny(tr::now, lt_count,seconds / 3600)));
  717. rpl::combine(
  718. labels->widthValue(),
  719. label->widthValue()
  720. ) | rpl::start_with_next([=](int outer, int inner) {
  721. const auto skip = st::localStorageLimitMargin;
  722. const auto size = st::localStorageLimitSlider.seekSize;
  723. const auto available = outer
  724. - skip.left()
  725. - skip.right()
  726. - size.width();
  727. const auto shift = (i == 0)
  728. ? -(size.width() / 2)
  729. : (i + 1 == kSlowmodeValues)
  730. ? (size.width() - (size.width() / 2) - inner)
  731. : (-inner / 2);
  732. const auto left = skip.left()
  733. + (size.width() / 2)
  734. + (i * available) / (kSlowmodeValues - 1)
  735. + shift;
  736. label->moveToLeft(left, 0, outer);
  737. }, label->lifetime());
  738. }
  739. }
  740. rpl::producer<int> AddSlowmodeSlider(
  741. not_null<Ui::VerticalLayout*> container,
  742. not_null<PeerData*> peer) {
  743. using namespace rpl::mappers;
  744. if (const auto chat = peer->asChat()) {
  745. if (!chat->amCreator()) {
  746. return rpl::single(0);
  747. }
  748. }
  749. const auto channel = peer->asChannel();
  750. auto &lifetime = container->lifetime();
  751. const auto secondsCount = lifetime.make_state<rpl::variable<int>>(
  752. channel ? channel->slowmodeSeconds() : 0);
  753. container->add(
  754. object_ptr<Ui::FlatLabel>(
  755. container,
  756. tr::lng_rights_slowmode_header(),
  757. st::rightsHeaderLabel),
  758. st::rightsHeaderMargin);
  759. AddSlowmodeLabels(container);
  760. const auto slider = container->add(
  761. object_ptr<Ui::MediaSlider>(container, st::localStorageLimitSlider),
  762. st::localStorageLimitMargin);
  763. slider->resize(st::localStorageLimitSlider.seekSize);
  764. slider->setPseudoDiscrete(
  765. kSlowmodeValues,
  766. SlowmodeDelayByIndex,
  767. secondsCount->current(),
  768. [=](int seconds) {
  769. (*secondsCount) = seconds;
  770. });
  771. auto hasSlowMode = secondsCount->value(
  772. ) | rpl::map(
  773. _1 != 0
  774. ) | rpl::distinct_until_changed();
  775. auto useSeconds = secondsCount->value(
  776. ) | rpl::map(
  777. _1 < 60
  778. ) | rpl::distinct_until_changed();
  779. auto interval = rpl::combine(
  780. std::move(useSeconds),
  781. tr::lng_rights_slowmode_interval_seconds(
  782. lt_count,
  783. secondsCount->value() | tr::to_count()),
  784. tr::lng_rights_slowmode_interval_minutes(
  785. lt_count,
  786. secondsCount->value() | rpl::map(_1 / 60.))
  787. ) | rpl::map([](
  788. bool use,
  789. const QString &seconds,
  790. const QString &minutes) {
  791. return use ? seconds : minutes;
  792. });
  793. auto aboutText = rpl::combine(
  794. std::move(hasSlowMode),
  795. tr::lng_rights_slowmode_about(),
  796. tr::lng_rights_slowmode_about_interval(
  797. lt_interval,
  798. std::move(interval))
  799. ) | rpl::map([](
  800. bool has,
  801. const QString &about,
  802. const QString &aboutInterval) {
  803. return has ? aboutInterval : about;
  804. });
  805. container->add(
  806. object_ptr<Ui::DividerLabel>(
  807. container,
  808. object_ptr<Ui::FlatLabel>(
  809. container,
  810. std::move(aboutText),
  811. st::boxDividerLabel),
  812. st::proxyAboutPadding),
  813. style::margins(0, st::infoProfileSkip, 0, st::infoProfileSkip));
  814. return secondsCount->value();
  815. }
  816. void AddBoostsUnrestrictLabels(
  817. not_null<Ui::VerticalLayout*> container,
  818. not_null<Main::Session*> session) {
  819. const auto labels = container->add(
  820. object_ptr<Ui::FixedHeightWidget>(container, st::normalFont->height),
  821. st::slowmodeLabelsMargin);
  822. const auto manager = &session->data().customEmojiManager();
  823. const auto one = Ui::Text::SingleCustomEmoji(
  824. manager->registerInternalEmoji(
  825. st::boostMessageIcon,
  826. st::boostMessageIconPadding));
  827. const auto many = Ui::Text::SingleCustomEmoji(
  828. manager->registerInternalEmoji(
  829. st::boostsMessageIcon,
  830. st::boostsMessageIconPadding));
  831. const auto context = Core::TextContext({
  832. .session = session,
  833. .customEmojiLoopLimit = 1,
  834. });
  835. for (auto i = 0; i != kBoostsUnrestrictValues; ++i) {
  836. const auto label = Ui::CreateChild<Ui::FlatLabel>(
  837. labels,
  838. st::boostsUnrestrictLabel);
  839. label->setMarkedText(
  840. TextWithEntities(i ? many : one).append(QString::number(i + 1)),
  841. context);
  842. rpl::combine(
  843. labels->widthValue(),
  844. label->widthValue()
  845. ) | rpl::start_with_next([=](int outer, int inner) {
  846. const auto skip = st::localStorageLimitMargin;
  847. const auto size = st::localStorageLimitSlider.seekSize;
  848. const auto available = outer
  849. - skip.left()
  850. - skip.right()
  851. - size.width();
  852. const auto shift = (i == 0)
  853. ? -(size.width() / 2)
  854. : (i + 1 == kBoostsUnrestrictValues)
  855. ? (size.width() - (size.width() / 2) - inner)
  856. : (-inner / 2);
  857. const auto left = skip.left()
  858. + (size.width() / 2)
  859. + (i * available) / (kBoostsUnrestrictValues - 1)
  860. + shift;
  861. label->moveToLeft(left, 0, outer);
  862. }, label->lifetime());
  863. }
  864. }
  865. rpl::producer<int> AddBoostsUnrestrictSlider(
  866. not_null<Ui::VerticalLayout*> container,
  867. not_null<PeerData*> peer) {
  868. using namespace rpl::mappers;
  869. if (const auto chat = peer->asChat()) {
  870. if (!chat->amCreator()) {
  871. return rpl::single(0);
  872. }
  873. }
  874. const auto channel = peer->asChannel();
  875. auto &lifetime = container->lifetime();
  876. const auto boostsUnrestrict = lifetime.make_state<rpl::variable<int>>(
  877. channel ? channel->boostsUnrestrict() : 0);
  878. Ui::AddSkip(container);
  879. auto enabled = boostsUnrestrict->value(
  880. ) | rpl::map(_1 > 0);
  881. container->add(object_ptr<Ui::SettingsButton>(
  882. container,
  883. tr::lng_rights_boosts_no_restrict(),
  884. st::defaultSettingsButton
  885. ))->toggleOn(rpl::duplicate(enabled))->toggledValue(
  886. ) | rpl::start_with_next([=](bool toggled) {
  887. if (toggled && !boostsUnrestrict->current()) {
  888. *boostsUnrestrict = 1;
  889. } else if (!toggled && boostsUnrestrict->current()) {
  890. *boostsUnrestrict = 0;
  891. }
  892. }, container->lifetime());
  893. const auto outer = container->add(
  894. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  895. container,
  896. object_ptr<Ui::VerticalLayout>(container)));
  897. outer->toggleOn(rpl::duplicate(enabled), anim::type::normal);
  898. outer->finishAnimating();
  899. const auto inner = outer->entity();
  900. AddBoostsUnrestrictLabels(inner, &peer->session());
  901. const auto slider = inner->add(
  902. object_ptr<Ui::MediaSlider>(inner, st::localStorageLimitSlider),
  903. st::localStorageLimitMargin);
  904. slider->resize(st::localStorageLimitSlider.seekSize);
  905. slider->setPseudoDiscrete(
  906. kBoostsUnrestrictValues,
  907. BoostsUnrestrictByIndex,
  908. boostsUnrestrict->current(),
  909. [=](int boosts) {
  910. (*boostsUnrestrict) = boosts;
  911. });
  912. inner->add(
  913. object_ptr<Ui::DividerLabel>(
  914. inner,
  915. object_ptr<Ui::FlatLabel>(
  916. inner,
  917. rpl::conditional(
  918. boostsUnrestrict->value() | rpl::map(_1 > 0),
  919. tr::lng_rights_boosts_about_on(),
  920. tr::lng_rights_boosts_about()),
  921. st::boxDividerLabel),
  922. st::proxyAboutPadding),
  923. style::margins(0, st::infoProfileSkip, 0, 0));
  924. return boostsUnrestrict->value();
  925. }
  926. rpl::producer<int> AddBoostsUnrestrictWrapped(
  927. not_null<Ui::VerticalLayout*> container,
  928. not_null<PeerData*> peer,
  929. rpl::producer<bool> shown) {
  930. const auto wrap = container->add(
  931. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  932. container,
  933. object_ptr<Ui::VerticalLayout>(container)));
  934. wrap->toggleOn(std::move(shown), anim::type::normal);
  935. wrap->finishAnimating();
  936. const auto inner = wrap->entity();
  937. auto result = AddBoostsUnrestrictSlider(inner, peer);
  938. const auto skip = st::defaultVerticalListSkip;
  939. const auto divider = inner->add(
  940. object_ptr<Ui::SlideWrap<Ui::BoxContentDivider>>(
  941. inner,
  942. object_ptr<Ui::BoxContentDivider>(inner),
  943. QMargins{ 0, skip, 0, skip }));
  944. divider->toggleOn(rpl::duplicate(result) | rpl::map(!rpl::mappers::_1));
  945. divider->finishAnimating();
  946. return result;
  947. }
  948. void AddSuggestGigagroup(
  949. not_null<Ui::VerticalLayout*> container,
  950. Fn<void()> callback) {
  951. container->add(
  952. object_ptr<Ui::FlatLabel>(
  953. container,
  954. tr::lng_rights_gigagroup_title(),
  955. st::rightsHeaderLabel),
  956. st::rightsHeaderMargin);
  957. container->add(EditPeerInfoBox::CreateButton(
  958. container,
  959. tr::lng_rights_gigagroup_convert(),
  960. rpl::single(QString()),
  961. std::move(callback),
  962. st::manageGroupTopicsButton,
  963. { &st::menuIconChatDiscuss }));
  964. container->add(
  965. object_ptr<Ui::DividerLabel>(
  966. container,
  967. object_ptr<Ui::FlatLabel>(
  968. container,
  969. tr::lng_rights_gigagroup_about(),
  970. st::boxDividerLabel),
  971. st::proxyAboutPadding),
  972. style::margins(0, st::infoProfileSkip, 0, st::infoProfileSkip));
  973. }
  974. void AddBannedButtons(
  975. not_null<Ui::VerticalLayout*> container,
  976. not_null<Window::SessionNavigation*> navigation,
  977. not_null<PeerData*> peer) {
  978. if (const auto chat = peer->asChat()) {
  979. if (!chat->amCreator()) {
  980. return;
  981. }
  982. }
  983. const auto channel = peer->asChannel();
  984. container->add(EditPeerInfoBox::CreateButton(
  985. container,
  986. tr::lng_manage_peer_exceptions(),
  987. (channel
  988. ? Info::Profile::RestrictedCountValue(channel)
  989. : rpl::single(0)) | ToPositiveNumberString(),
  990. [=] {
  991. ParticipantsBoxController::Start(
  992. navigation,
  993. peer,
  994. ParticipantsBoxController::Role::Restricted);
  995. },
  996. st::manageGroupTopicsButton,
  997. { &st::menuIconPermissions }));
  998. if (channel) {
  999. container->add(EditPeerInfoBox::CreateButton(
  1000. container,
  1001. tr::lng_manage_peer_removed_users(),
  1002. Info::Profile::KickedCountValue(channel)
  1003. | ToPositiveNumberString(),
  1004. [=] {
  1005. ParticipantsBoxController::Start(
  1006. navigation,
  1007. peer,
  1008. ParticipantsBoxController::Role::Kicked);
  1009. },
  1010. st::manageGroupTopicsButton,
  1011. { &st::menuIconRemove }));
  1012. }
  1013. }
  1014. } // namespace
  1015. void ShowEditPeerPermissionsBox(
  1016. not_null<Ui::GenericBox*> box,
  1017. not_null<Window::SessionNavigation*> navigation,
  1018. not_null<PeerData*> channelOrGroup,
  1019. Fn<void(EditPeerPermissionsBoxResult)> done) {
  1020. const auto peer = channelOrGroup->migrateToOrMe();
  1021. box->setTitle(tr::lng_manage_peer_permissions());
  1022. const auto inner = box->verticalLayout();
  1023. using Flag = ChatRestriction;
  1024. using Flags = ChatRestrictions;
  1025. const auto disabledByAdminRights = DisabledByAdminRights(peer);
  1026. const auto restrictions = FixDependentRestrictions([&] {
  1027. if (const auto chat = peer->asChat()) {
  1028. return chat->defaultRestrictions()
  1029. | disabledByAdminRights;
  1030. } else if (const auto channel = peer->asChannel()) {
  1031. return channel->defaultRestrictions()
  1032. | (channel->isPublic()
  1033. ? (Flag::ChangeInfo | Flag::PinMessages)
  1034. : Flags(0))
  1035. | disabledByAdminRights;
  1036. }
  1037. Unexpected("User in EditPeerPermissionsBox.");
  1038. }());
  1039. const auto disabledMessages = [&] {
  1040. auto result = base::flat_map<Flags, QString>();
  1041. result.emplace(
  1042. disabledByAdminRights,
  1043. tr::lng_rights_permission_cant_edit(tr::now));
  1044. if (const auto channel = peer->asChannel()) {
  1045. if (channel->isPublic()) {
  1046. result.emplace(
  1047. Flag::ChangeInfo | Flag::PinMessages,
  1048. tr::lng_rights_permission_unavailable(tr::now));
  1049. } else if (channel->isMegagroup() && channel->linkedChat()) {
  1050. result.emplace(
  1051. Flag::ChangeInfo | Flag::PinMessages,
  1052. tr::lng_rights_permission_in_discuss(tr::now));
  1053. }
  1054. }
  1055. return result;
  1056. }();
  1057. Ui::AddSubsectionTitle(
  1058. inner,
  1059. tr::lng_rights_default_restrictions_header());
  1060. auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
  1061. inner,
  1062. restrictions,
  1063. disabledMessages,
  1064. { .isForum = peer->isForum() });
  1065. inner->add(std::move(checkboxes));
  1066. struct State {
  1067. rpl::variable<int> slowmodeSeconds;
  1068. rpl::variable<int> boostsUnrestrict;
  1069. rpl::variable<bool> hasSendRestrictions;
  1070. rpl::variable<int> starsPerMessage;
  1071. };
  1072. const auto state = inner->lifetime().make_state<State>();
  1073. const auto channel = peer->asChannel();
  1074. const auto available = channel && channel->paidMessagesAvailable();
  1075. Ui::AddSkip(inner);
  1076. Ui::AddDivider(inner);
  1077. auto charging = (Ui::SettingsButton*)nullptr;
  1078. if (available) {
  1079. Ui::AddSkip(inner);
  1080. const auto starsPerMessage = peer->isChannel()
  1081. ? peer->asChannel()->starsPerMessage()
  1082. : 0;
  1083. charging = inner->add(object_ptr<Ui::SettingsButton>(
  1084. inner,
  1085. tr::lng_rights_charge_stars(),
  1086. st::settingsButtonNoIcon));
  1087. charging->toggleOn(rpl::single(starsPerMessage > 0));
  1088. Ui::AddSkip(inner);
  1089. Ui::AddDividerText(inner, tr::lng_rights_charge_stars_about());
  1090. const auto chargeWrap = inner->add(
  1091. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  1092. inner,
  1093. object_ptr<Ui::VerticalLayout>(inner)));
  1094. chargeWrap->toggleOn(charging->toggledValue());
  1095. chargeWrap->finishAnimating();
  1096. const auto chargeInner = chargeWrap->entity();
  1097. Ui::AddSkip(chargeInner);
  1098. state->starsPerMessage = SetupChargeSlider(
  1099. chargeInner,
  1100. peer,
  1101. starsPerMessage);
  1102. }
  1103. static constexpr auto kSendRestrictions = Flag::EmbedLinks
  1104. | Flag::SendGames
  1105. | Flag::SendGifs
  1106. | Flag::SendInline
  1107. | Flag::SendPolls
  1108. | Flag::SendStickers
  1109. | Flag::SendPhotos
  1110. | Flag::SendVideos
  1111. | Flag::SendVideoMessages
  1112. | Flag::SendMusic
  1113. | Flag::SendVoiceMessages
  1114. | Flag::SendFiles
  1115. | Flag::SendOther;
  1116. state->hasSendRestrictions = ((restrictions & kSendRestrictions) != 0)
  1117. || (peer->isChannel() && peer->asChannel()->slowmodeSeconds() > 0);
  1118. state->boostsUnrestrict = AddBoostsUnrestrictWrapped(
  1119. inner,
  1120. peer,
  1121. state->hasSendRestrictions.value());
  1122. state->slowmodeSeconds = AddSlowmodeSlider(inner, peer);
  1123. state->hasSendRestrictions = rpl::combine(
  1124. rpl::single(
  1125. restrictions
  1126. ) | rpl::then(std::move(changes)),
  1127. state->slowmodeSeconds.value()
  1128. ) | rpl::map([](ChatRestrictions restrictions, int slowmodeSeconds) {
  1129. return ((restrictions & kSendRestrictions) != 0) || slowmodeSeconds;
  1130. });
  1131. if (const auto channel = peer->asChannel()) {
  1132. constexpr auto kThresholdOffset = int(1000);
  1133. const auto threshold = -kThresholdOffset
  1134. + channel->session().serverConfig().megagroupSizeMax;
  1135. if (channel->amCreator()
  1136. && channel->membersCount() >= threshold) {
  1137. AddSuggestGigagroup(
  1138. inner,
  1139. AboutGigagroupCallback(
  1140. peer->asChannel(),
  1141. navigation->parentController()));
  1142. }
  1143. }
  1144. AddBannedButtons(inner, navigation, peer);
  1145. box->addButton(tr::lng_settings_save(), [=, rights = getRestrictions] {
  1146. const auto restrictions = rights();
  1147. const auto slowmodeSeconds = state->slowmodeSeconds.current();
  1148. const auto hasRestrictions = (slowmodeSeconds > 0)
  1149. || ((restrictions & kSendRestrictions) != 0);
  1150. const auto boostsUnrestrict = hasRestrictions
  1151. ? state->boostsUnrestrict.current()
  1152. : 0;
  1153. const auto starsPerMessage = (charging && charging->toggled())
  1154. ? state->starsPerMessage.current()
  1155. : 0;
  1156. done({
  1157. restrictions,
  1158. slowmodeSeconds,
  1159. boostsUnrestrict,
  1160. starsPerMessage,
  1161. });
  1162. });
  1163. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  1164. box->setWidth(st::boxWideWidth);
  1165. }
  1166. Fn<void()> AboutGigagroupCallback(
  1167. not_null<ChannelData*> channel,
  1168. not_null<Window::SessionController*> controller) {
  1169. const auto weak = base::make_weak(controller);
  1170. const auto converting = std::make_shared<bool>();
  1171. const auto convertSure = [=] {
  1172. if (*converting) {
  1173. return;
  1174. }
  1175. *converting = true;
  1176. channel->session().api().request(MTPchannels_ConvertToGigagroup(
  1177. channel->inputChannel
  1178. )).done([=](const MTPUpdates &result) {
  1179. channel->session().api().applyUpdates(result);
  1180. if (const auto strong = weak.get()) {
  1181. strong->window().hideSettingsAndLayer();
  1182. strong->showToast(tr::lng_gigagroup_done(tr::now));
  1183. }
  1184. }).fail([=] {
  1185. *converting = false;
  1186. }).send();
  1187. };
  1188. const auto convertWarn = [=] {
  1189. const auto strong = weak.get();
  1190. if (*converting || !strong) {
  1191. return;
  1192. }
  1193. strong->show(Box([=](not_null<Ui::GenericBox*> box) {
  1194. box->setTitle(tr::lng_gigagroup_warning_title());
  1195. box->addRow(
  1196. object_ptr<Ui::FlatLabel>(
  1197. box,
  1198. tr::lng_gigagroup_warning(
  1199. ) | Ui::Text::ToRichLangValue(),
  1200. st::infoAboutGigagroup));
  1201. box->addButton(tr::lng_gigagroup_convert_sure(), convertSure);
  1202. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  1203. }));
  1204. };
  1205. return [=] {
  1206. const auto strong = weak.get();
  1207. if (*converting || !strong) {
  1208. return;
  1209. }
  1210. strong->show(Box([=](not_null<Ui::GenericBox*> box) {
  1211. box->setTitle(tr::lng_gigagroup_convert_title());
  1212. const auto addFeature = [&](rpl::producer<QString> text) {
  1213. using namespace rpl::mappers;
  1214. const auto prefix = QString::fromUtf8("\xE2\x80\xA2 ");
  1215. box->addRow(
  1216. object_ptr<Ui::FlatLabel>(
  1217. box,
  1218. std::move(text) | rpl::map(prefix + _1),
  1219. st::infoAboutGigagroup),
  1220. style::margins(
  1221. st::boxRowPadding.left(),
  1222. st::boxLittleSkip,
  1223. st::boxRowPadding.right(),
  1224. st::boxLittleSkip));
  1225. };
  1226. addFeature(tr::lng_gigagroup_convert_feature1());
  1227. addFeature(tr::lng_gigagroup_convert_feature2());
  1228. addFeature(tr::lng_gigagroup_convert_feature3());
  1229. box->addButton(tr::lng_gigagroup_convert_sure(), convertWarn);
  1230. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  1231. }));
  1232. };
  1233. }
  1234. std::vector<RestrictionLabel> RestrictionLabels(
  1235. Data::RestrictionsSetOptions options) {
  1236. auto result = std::vector<RestrictionLabel>();
  1237. for (const auto &[_, r] : NestedRestrictionLabelsList(options)) {
  1238. result.insert(result.end(), r.begin(), r.end());
  1239. }
  1240. return result;
  1241. }
  1242. std::vector<AdminRightLabel> AdminRightLabels(
  1243. Data::AdminRightsSetOptions options) {
  1244. auto result = std::vector<AdminRightLabel>();
  1245. for (const auto &[_, r] : NestedAdminRightLabels(options)) {
  1246. result.insert(result.end(), r.begin(), r.end());
  1247. }
  1248. return result;
  1249. }
  1250. EditFlagsControl<ChatRestrictions> CreateEditRestrictions(
  1251. QWidget *parent,
  1252. ChatRestrictions restrictions,
  1253. base::flat_map<ChatRestrictions, QString> disabledMessages,
  1254. Data::RestrictionsSetOptions options) {
  1255. auto widget = object_ptr<Ui::VerticalLayout>(parent);
  1256. auto result = CreateEditFlags(
  1257. widget.data(),
  1258. NegateRestrictions(restrictions),
  1259. {
  1260. .labels = NestedRestrictionLabelsList(options),
  1261. .disabledMessages = std::move(disabledMessages),
  1262. });
  1263. result.widget = std::move(widget);
  1264. result.value = [original = std::move(result.value)]{
  1265. return NegateRestrictions(original());
  1266. };
  1267. result.changes = std::move(
  1268. result.changes
  1269. ) | rpl::map(NegateRestrictions);
  1270. return result;
  1271. }
  1272. EditFlagsControl<ChatAdminRights> CreateEditAdminRights(
  1273. QWidget *parent,
  1274. ChatAdminRights rights,
  1275. base::flat_map<ChatAdminRights, QString> disabledMessages,
  1276. Data::AdminRightsSetOptions options) {
  1277. auto widget = object_ptr<Ui::VerticalLayout>(parent);
  1278. auto result = CreateEditFlags(
  1279. widget.data(),
  1280. rights,
  1281. {
  1282. .labels = NestedAdminRightLabels(options),
  1283. .disabledMessages = std::move(disabledMessages),
  1284. });
  1285. result.widget = std::move(widget);
  1286. return result;
  1287. }
  1288. ChatAdminRights DisabledByDefaultRestrictions(not_null<PeerData*> peer) {
  1289. using Flag = ChatAdminRight;
  1290. using Restriction = ChatRestriction;
  1291. const auto restrictions = FixDependentRestrictions([&] {
  1292. if (const auto chat = peer->asChat()) {
  1293. return chat->defaultRestrictions();
  1294. } else if (const auto channel = peer->asChannel()) {
  1295. return channel->defaultRestrictions();
  1296. }
  1297. Unexpected("User in DisabledByDefaultRestrictions.");
  1298. }());
  1299. return Flag(0)
  1300. | ((restrictions & Restriction::PinMessages)
  1301. ? Flag(0)
  1302. : Flag::PinMessages)
  1303. //
  1304. // We allow to edit 'invite_users' admin right no matter what
  1305. // is chosen in default permissions for 'invite_users', because
  1306. // if everyone can 'invite_users' it handles invite link for admins.
  1307. //
  1308. //| ((restrictions & Restriction::AddParticipants)
  1309. // ? Flag(0)
  1310. // : Flag::InviteByLinkOrAdd)
  1311. //
  1312. | ((restrictions & Restriction::ChangeInfo)
  1313. ? Flag(0)
  1314. : Flag::ChangeInfo);
  1315. }
  1316. ChatRestrictions FixDependentRestrictions(ChatRestrictions restrictions) {
  1317. const auto &dependencies = Dependencies(restrictions);
  1318. // Fix iOS bug of saving send_inline like embed_links.
  1319. // We copy send_stickers to send_inline.
  1320. if (restrictions & ChatRestriction::SendStickers) {
  1321. restrictions |= ChatRestriction::SendInline;
  1322. } else {
  1323. restrictions &= ~ChatRestriction::SendInline;
  1324. }
  1325. // Apply the strictest.
  1326. const auto fixOne = [&] {
  1327. for (const auto &[first, second] : dependencies) {
  1328. if ((restrictions & second) && !(restrictions & first)) {
  1329. restrictions |= first;
  1330. return true;
  1331. }
  1332. }
  1333. return false;
  1334. };
  1335. while (fixOne()) {
  1336. }
  1337. return restrictions;
  1338. }
  1339. ChatAdminRights AdminRightsForOwnershipTransfer(
  1340. Data::AdminRightsSetOptions options) {
  1341. auto result = ChatAdminRights();
  1342. for (const auto &entry : AdminRightLabels(options)) {
  1343. if (!(entry.flags & ChatAdminRight::Anonymous)) {
  1344. result |= entry.flags;
  1345. }
  1346. }
  1347. return result;
  1348. }
  1349. EditFlagsControl<PowerSaving::Flags> CreateEditPowerSaving(
  1350. QWidget *parent,
  1351. PowerSaving::Flags flags,
  1352. rpl::producer<QString> forceDisabledMessage) {
  1353. auto widget = object_ptr<Ui::VerticalLayout>(parent);
  1354. auto descriptor = Settings::PowerSavingLabels();
  1355. descriptor.forceDisabledMessage = std::move(forceDisabledMessage);
  1356. auto result = CreateEditFlags(
  1357. widget.data(),
  1358. flags,
  1359. std::move(descriptor));
  1360. result.widget = std::move(widget);
  1361. return result;
  1362. }
  1363. EditFlagsControl<AdminLog::FilterValue::Flags> CreateEditAdminLogFilter(
  1364. QWidget *parent,
  1365. AdminLog::FilterValue::Flags flags,
  1366. bool isChannel) {
  1367. auto widget = object_ptr<Ui::VerticalLayout>(parent);
  1368. auto descriptor = AdminLog::FilterValueLabels(isChannel);
  1369. auto result = CreateEditFlags(
  1370. widget.data(),
  1371. flags,
  1372. std::move(descriptor));
  1373. result.widget = std::move(widget);
  1374. return result;
  1375. }