premium_limits_box.cpp 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195
  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/premium_limits_box.h"
  8. #include "ui/boxes/confirm_box.h"
  9. #include "ui/controls/peer_list_dummy.h"
  10. #include "ui/effects/premium_bubble.h"
  11. #include "ui/effects/premium_graphics.h"
  12. #include "ui/widgets/checkbox.h"
  13. #include "ui/wrap/padding_wrap.h"
  14. #include "ui/text/text_utilities.h"
  15. #include "ui/vertical_list.h"
  16. #include "main/main_session.h"
  17. #include "main/main_account.h"
  18. #include "main/main_domain.h"
  19. #include "boxes/peer_list_controllers.h"
  20. #include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox
  21. #include "window/window_session_controller.h"
  22. #include "data/data_chat_filters.h"
  23. #include "data/data_user.h"
  24. #include "data/data_channel.h"
  25. #include "data/data_forum.h"
  26. #include "data/data_saved_messages.h"
  27. #include "data/data_session.h"
  28. #include "data/data_folder.h"
  29. #include "data/data_premium_limits.h"
  30. #include "lang/lang_keys.h"
  31. #include "settings/settings_premium.h" // ShowPremium.
  32. #include "base/unixtime.h"
  33. #include "apiwrap.h"
  34. #include "styles/style_premium.h"
  35. #include "styles/style_boxes.h"
  36. #include "styles/style_layers.h"
  37. #include "styles/style_info.h"
  38. #include "styles/style_settings.h"
  39. namespace {
  40. struct InfographicDescriptor {
  41. float64 defaultLimit = 0;
  42. float64 current = 0;
  43. float64 premiumLimit = 0;
  44. const style::icon *icon;
  45. std::optional<tr::phrase<lngtag_count>> phrase;
  46. bool complexRatio = false;
  47. };
  48. void AddSubtitle(
  49. not_null<Ui::VerticalLayout*> container,
  50. rpl::producer<QString> text) {
  51. const auto &subtitlePadding = st::settingsButton.padding;
  52. Ui::AddSubsectionTitle(
  53. container,
  54. std::move(text),
  55. { 0, subtitlePadding.top(), 0, -subtitlePadding.bottom() });
  56. }
  57. class InactiveController final : public PeerListController {
  58. public:
  59. explicit InactiveController(not_null<Main::Session*> session);
  60. ~InactiveController();
  61. Main::Session &session() const override;
  62. void prepare() override;
  63. void rowClicked(not_null<PeerListRow*> row) override;
  64. [[nodiscard]] rpl::producer<int> countValue() const;
  65. private:
  66. void appendRow(not_null<PeerData*> peer, TimeId date);
  67. [[nodiscard]] std::unique_ptr<PeerListRow> createRow(
  68. not_null<PeerData*> peer,
  69. TimeId date) const;
  70. const not_null<Main::Session*> _session;
  71. rpl::variable<int> _count;
  72. mtpRequestId _requestId = 0;
  73. };
  74. class PublicsController final : public PeerListController {
  75. public:
  76. PublicsController(
  77. not_null<Window::SessionNavigation*> navigation,
  78. Fn<void()> closeBox);
  79. ~PublicsController();
  80. Main::Session &session() const override;
  81. void prepare() override;
  82. void rowClicked(not_null<PeerListRow*> row) override;
  83. void rowRightActionClicked(not_null<PeerListRow*> row) override;
  84. [[nodiscard]] rpl::producer<int> countValue() const;
  85. private:
  86. void appendRow(not_null<PeerData*> peer);
  87. [[nodiscard]] std::unique_ptr<PeerListRow> createRow(
  88. not_null<PeerData*> peer) const;
  89. const not_null<Window::SessionNavigation*> _navigation;
  90. rpl::variable<int> _count;
  91. Fn<void()> _closeBox;
  92. mtpRequestId _requestId = 0;
  93. };
  94. class InactiveDelegate final : public PeerListContentDelegate {
  95. public:
  96. void peerListSetTitle(rpl::producer<QString> title) override;
  97. void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
  98. bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
  99. int peerListSelectedRowsCount() override;
  100. void peerListScrollToTop() override;
  101. void peerListAddSelectedPeerInBunch(
  102. not_null<PeerData*> peer) override;
  103. void peerListAddSelectedRowInBunch(
  104. not_null<PeerListRow*> row) override;
  105. void peerListFinishSelectedRowsBunch() override;
  106. void peerListSetDescription(
  107. object_ptr<Ui::FlatLabel> description) override;
  108. std::shared_ptr<Main::SessionShow> peerListUiShow() override;
  109. void peerListSetRowChecked(
  110. not_null<PeerListRow*> row,
  111. bool checked) override;
  112. [[nodiscard]] rpl::producer<int> selectedCountChanges() const;
  113. [[nodiscard]] const base::flat_set<PeerListRowId> &selected() const;
  114. private:
  115. base::flat_set<PeerListRowId> _selectedIds;
  116. rpl::event_stream<int> _selectedCountChanges;
  117. };
  118. [[nodiscard]] Ui::Premium::BubbleType ChooseBubbleType(bool premium) {
  119. return premium
  120. ? Ui::Premium::BubbleType::Premium
  121. : Ui::Premium::BubbleType::NoPremium;
  122. }
  123. void InactiveDelegate::peerListSetTitle(rpl::producer<QString> title) {
  124. }
  125. void InactiveDelegate::peerListSetAdditionalTitle(
  126. rpl::producer<QString> title) {
  127. }
  128. bool InactiveDelegate::peerListIsRowChecked(not_null<PeerListRow*> row) {
  129. return _selectedIds.contains(row->id());
  130. }
  131. int InactiveDelegate::peerListSelectedRowsCount() {
  132. return int(_selectedIds.size());
  133. }
  134. void InactiveDelegate::peerListScrollToTop() {
  135. }
  136. void InactiveDelegate::peerListAddSelectedPeerInBunch(
  137. not_null<PeerData*> peer) {
  138. _selectedIds.emplace(PeerListRowId(peer->id.value));
  139. _selectedCountChanges.fire(int(_selectedIds.size()));
  140. }
  141. void InactiveDelegate::peerListAddSelectedRowInBunch(
  142. not_null<PeerListRow*> row) {
  143. _selectedIds.emplace(row->id());
  144. _selectedCountChanges.fire(int(_selectedIds.size()));
  145. }
  146. void InactiveDelegate::peerListSetRowChecked(
  147. not_null<PeerListRow*> row,
  148. bool checked) {
  149. if (checked) {
  150. _selectedIds.emplace(row->id());
  151. } else {
  152. _selectedIds.remove(row->id());
  153. }
  154. _selectedCountChanges.fire(int(_selectedIds.size()));
  155. PeerListContentDelegate::peerListSetRowChecked(row, checked);
  156. }
  157. void InactiveDelegate::peerListFinishSelectedRowsBunch() {
  158. }
  159. void InactiveDelegate::peerListSetDescription(
  160. object_ptr<Ui::FlatLabel> description) {
  161. description.destroy();
  162. }
  163. std::shared_ptr<Main::SessionShow> InactiveDelegate::peerListUiShow() {
  164. Unexpected("...InactiveDelegate::peerListUiShow");
  165. }
  166. rpl::producer<int> InactiveDelegate::selectedCountChanges() const {
  167. return _selectedCountChanges.events();
  168. }
  169. const base::flat_set<PeerListRowId> &InactiveDelegate::selected() const {
  170. return _selectedIds;
  171. }
  172. InactiveController::InactiveController(not_null<Main::Session*> session)
  173. : _session(session) {
  174. }
  175. InactiveController::~InactiveController() {
  176. if (_requestId) {
  177. _session->api().request(_requestId).cancel();
  178. }
  179. }
  180. Main::Session &InactiveController::session() const {
  181. return *_session;
  182. }
  183. void InactiveController::prepare() {
  184. _requestId = _session->api().request(MTPchannels_GetInactiveChannels(
  185. )).done([=](const MTPmessages_InactiveChats &result) {
  186. _requestId = 0;
  187. const auto &data = result.data();
  188. _session->data().processUsers(data.vusers());
  189. const auto &list = data.vchats().v;
  190. const auto &dates = data.vdates().v;
  191. for (auto i = 0, count = int(list.size()); i != count; ++i) {
  192. const auto peer = _session->data().processChat(list[i]);
  193. const auto date = (i < dates.size()) ? dates[i].v : TimeId();
  194. appendRow(peer, date);
  195. }
  196. delegate()->peerListRefreshRows();
  197. _count = delegate()->peerListFullRowsCount();
  198. }).send();
  199. }
  200. void InactiveController::rowClicked(not_null<PeerListRow*> row) {
  201. delegate()->peerListSetRowChecked(row, !row->checked());
  202. }
  203. rpl::producer<int> InactiveController::countValue() const {
  204. return _count.value();
  205. }
  206. void InactiveController::appendRow(
  207. not_null<PeerData*> participant,
  208. TimeId date) {
  209. if (!delegate()->peerListFindRow(participant->id.value)) {
  210. delegate()->peerListAppendRow(createRow(participant, date));
  211. }
  212. }
  213. std::unique_ptr<PeerListRow> InactiveController::createRow(
  214. not_null<PeerData*> peer,
  215. TimeId date) const {
  216. auto result = std::make_unique<PeerListRow>(peer);
  217. const auto active = base::unixtime::parse(date).date();
  218. const auto now = QDate::currentDate();
  219. const auto time = [&] {
  220. const auto days = active.daysTo(now);
  221. if (now < active) {
  222. return QString();
  223. } else if (active == now) {
  224. const auto unixtime = base::unixtime::now();
  225. const auto delta = int64(unixtime) - int64(date);
  226. if (delta <= 0) {
  227. return QString();
  228. } else if (delta >= 3600) {
  229. return tr::lng_hours(tr::now, lt_count, delta / 3600);
  230. } else if (delta >= 60) {
  231. return tr::lng_minutes(tr::now, lt_count, delta / 60);
  232. } else {
  233. return tr::lng_seconds(tr::now, lt_count, delta);
  234. }
  235. } else if (days >= 365) {
  236. return tr::lng_years(tr::now, lt_count, days / 365);
  237. } else if (days >= 31) {
  238. return tr::lng_months(tr::now, lt_count, days / 31);
  239. } else if (days >= 7) {
  240. return tr::lng_weeks(tr::now, lt_count, days / 7);
  241. } else {
  242. return tr::lng_days(tr::now, lt_count, days);
  243. }
  244. }();
  245. result->setCustomStatus(tr::lng_channels_leave_status(
  246. tr::now,
  247. lt_type,
  248. (peer->isBroadcast()
  249. ? tr::lng_channel_status(tr::now)
  250. : tr::lng_group_status(tr::now)),
  251. lt_time,
  252. time));
  253. return result;
  254. }
  255. PublicsController::PublicsController(
  256. not_null<Window::SessionNavigation*> navigation,
  257. Fn<void()> closeBox)
  258. : _navigation(navigation)
  259. , _closeBox(std::move(closeBox)) {
  260. }
  261. PublicsController::~PublicsController() {
  262. if (_requestId) {
  263. _navigation->session().api().request(_requestId).cancel();
  264. }
  265. }
  266. Main::Session &PublicsController::session() const {
  267. return _navigation->session();
  268. }
  269. rpl::producer<int> PublicsController::countValue() const {
  270. return _count.value();
  271. }
  272. void PublicsController::prepare() {
  273. _requestId = _navigation->session().api().request(
  274. MTPchannels_GetAdminedPublicChannels(MTP_flags(0))
  275. ).done([=](const MTPmessages_Chats &result) {
  276. _requestId = 0;
  277. const auto &chats = result.match([](const auto &data) {
  278. return data.vchats().v;
  279. });
  280. auto &owner = _navigation->session().data();
  281. for (const auto &chat : chats) {
  282. if (const auto peer = owner.processChat(chat)) {
  283. if (!peer->isChannel() || peer->username().isEmpty()) {
  284. continue;
  285. }
  286. appendRow(peer);
  287. }
  288. delegate()->peerListRefreshRows();
  289. }
  290. _count = delegate()->peerListFullRowsCount();
  291. }).send();
  292. }
  293. void PublicsController::rowClicked(not_null<PeerListRow*> row) {
  294. _navigation->parentController()->show(
  295. PrepareShortInfoBox(row->peer(), _navigation));
  296. }
  297. void PublicsController::rowRightActionClicked(not_null<PeerListRow*> row) {
  298. const auto peer = row->peer();
  299. const auto textMethod = peer->isMegagroup()
  300. ? tr::lng_channels_too_much_public_revoke_confirm_group
  301. : tr::lng_channels_too_much_public_revoke_confirm_channel;
  302. const auto text = textMethod(
  303. tr::now,
  304. lt_link,
  305. peer->session().createInternalLink(peer->username()),
  306. lt_group,
  307. peer->name());
  308. const auto confirmText = tr::lng_channels_too_much_public_revoke(
  309. tr::now);
  310. const auto closeBox = _closeBox;
  311. const auto once = std::make_shared<bool>(false);
  312. auto callback = crl::guard(_navigation, [=](Fn<void()> close) {
  313. if (*once) {
  314. return;
  315. }
  316. *once = true;
  317. peer->session().api().request(MTPchannels_UpdateUsername(
  318. peer->asChannel()->inputChannel,
  319. MTP_string()
  320. )).done([=] {
  321. peer->session().api().request(MTPchannels_DeactivateAllUsernames(
  322. peer->asChannel()->inputChannel
  323. )).done([=] {
  324. closeBox();
  325. close();
  326. }).send();
  327. }).send();
  328. });
  329. _navigation->parentController()->show(
  330. Ui::MakeConfirmBox({
  331. .text = text,
  332. .confirmed = std::move(callback),
  333. .confirmText = confirmText,
  334. }));
  335. }
  336. void PublicsController::appendRow(not_null<PeerData*> participant) {
  337. if (!delegate()->peerListFindRow(participant->id.value)) {
  338. delegate()->peerListAppendRow(createRow(participant));
  339. }
  340. }
  341. std::unique_ptr<PeerListRow> PublicsController::createRow(
  342. not_null<PeerData*> peer) const {
  343. auto result = std::make_unique<PeerListRowWithLink>(peer);
  344. result->setActionLink(tr::lng_channels_too_much_public_revoke(tr::now));
  345. result->setCustomStatus(
  346. _navigation->session().createInternalLink(peer->username()));
  347. return result;
  348. }
  349. void SimpleLimitBox(
  350. not_null<Ui::GenericBox*> box,
  351. const style::PremiumLimits *stOverride,
  352. not_null<Main::Session*> session,
  353. bool premiumPossible,
  354. rpl::producer<QString> title,
  355. rpl::producer<TextWithEntities> text,
  356. const QString &refAddition,
  357. const InfographicDescriptor &descriptor,
  358. bool fixed = false) {
  359. const auto &st = stOverride ? *stOverride : st::defaultPremiumLimits;
  360. box->setWidth(st::boxWideWidth);
  361. const auto top = fixed
  362. ? box->setPinnedToTopContent(object_ptr<Ui::VerticalLayout>(box))
  363. : box->verticalLayout();
  364. Ui::AddSkip(top, st::premiumInfographicPadding.top());
  365. Ui::Premium::AddBubbleRow(
  366. top,
  367. st::defaultPremiumBubble,
  368. BoxShowFinishes(box),
  369. 0,
  370. descriptor.current,
  371. (descriptor.complexRatio
  372. ? descriptor.premiumLimit
  373. : 2 * descriptor.current),
  374. ChooseBubbleType(premiumPossible),
  375. descriptor.phrase,
  376. descriptor.icon);
  377. Ui::AddSkip(top, st::premiumLineTextSkip);
  378. if (premiumPossible) {
  379. Ui::Premium::AddLimitRow(
  380. top,
  381. st,
  382. descriptor.premiumLimit,
  383. descriptor.phrase,
  384. 0,
  385. (descriptor.complexRatio
  386. ? (float64(descriptor.current) / descriptor.premiumLimit)
  387. : Ui::Premium::kLimitRowRatio));
  388. Ui::AddSkip(top, st::premiumInfographicPadding.bottom());
  389. }
  390. box->setTitle(std::move(title));
  391. auto padding = st::boxPadding;
  392. padding.setTop(padding.bottom());
  393. top->add(
  394. object_ptr<Ui::FlatLabel>(
  395. box,
  396. std::move(text),
  397. st::aboutRevokePublicLabel),
  398. padding);
  399. if (session->premium() || !premiumPossible) {
  400. box->addButton(tr::lng_box_ok(), [=] {
  401. box->closeBox();
  402. });
  403. } else {
  404. box->addButton(tr::lng_limits_increase(), [=] {
  405. Settings::ShowPremium(session, LimitsPremiumRef(refAddition));
  406. });
  407. box->addButton(tr::lng_cancel(), [=] {
  408. box->closeBox();
  409. });
  410. }
  411. if (fixed) {
  412. Ui::AddSkip(top, st::settingsButton.padding.bottom());
  413. Ui::AddDivider(top);
  414. }
  415. }
  416. void SimpleLimitBox(
  417. not_null<Ui::GenericBox*> box,
  418. const style::PremiumLimits *stOverride,
  419. not_null<Main::Session*> session,
  420. rpl::producer<QString> title,
  421. rpl::producer<TextWithEntities> text,
  422. const QString &refAddition,
  423. const InfographicDescriptor &descriptor,
  424. bool fixed = false) {
  425. SimpleLimitBox(
  426. box,
  427. stOverride,
  428. session,
  429. session->premiumPossible(),
  430. std::move(title),
  431. std::move(text),
  432. refAddition,
  433. descriptor,
  434. fixed);
  435. }
  436. [[nodiscard]] int PinsCount(not_null<Dialogs::MainList*> list) {
  437. return list->pinned()->order().size();
  438. }
  439. void SimplePinsLimitBox(
  440. not_null<Ui::GenericBox*> box,
  441. not_null<Main::Session*> session,
  442. const QString &refAddition,
  443. float64 defaultLimit,
  444. float64 premiumLimit,
  445. float64 currentCount) {
  446. const auto premium = session->premium();
  447. const auto premiumPossible = session->premiumPossible();
  448. const auto current = std::clamp(currentCount, defaultLimit, premiumLimit);
  449. auto text = rpl::combine(
  450. tr::lng_filter_pin_limit1(
  451. lt_count,
  452. rpl::single(premium ? premiumLimit : defaultLimit),
  453. Ui::Text::RichLangValue),
  454. ((premium || !premiumPossible)
  455. ? rpl::single(TextWithEntities())
  456. : tr::lng_filter_pin_limit2(
  457. lt_count,
  458. rpl::single(premiumLimit),
  459. Ui::Text::RichLangValue))
  460. ) | rpl::map([](TextWithEntities &&a, TextWithEntities &&b) {
  461. return b.text.isEmpty()
  462. ? a
  463. : a.append(QChar(' ')).append(std::move(b));
  464. });
  465. SimpleLimitBox(
  466. box,
  467. nullptr,
  468. session,
  469. tr::lng_filter_pin_limit_title(),
  470. std::move(text),
  471. refAddition,
  472. { defaultLimit, current, premiumLimit, &st::premiumIconPins });
  473. }
  474. } // namespace
  475. void ChannelsLimitBox(
  476. not_null<Ui::GenericBox*> box,
  477. not_null<Main::Session*> session) {
  478. const auto premium = session->premium();
  479. const auto premiumPossible = session->premiumPossible();
  480. const auto limits = Data::PremiumLimits(session);
  481. const auto defaultLimit = float64(limits.channelsDefault());
  482. const auto premiumLimit = float64(limits.channelsPremium());
  483. const auto current = (premium ? premiumLimit : defaultLimit);
  484. auto text = rpl::combine(
  485. tr::lng_channels_limit1(
  486. lt_count,
  487. rpl::single(current),
  488. Ui::Text::RichLangValue),
  489. ((premium || !premiumPossible)
  490. ? tr::lng_channels_limit2_final(Ui::Text::RichLangValue)
  491. : tr::lng_channels_limit2(
  492. lt_count,
  493. rpl::single(premiumLimit),
  494. Ui::Text::RichLangValue))
  495. ) | rpl::map([](TextWithEntities &&a, TextWithEntities &&b) {
  496. return a.append(QChar(' ')).append(std::move(b));
  497. });
  498. SimpleLimitBox(
  499. box,
  500. nullptr,
  501. session,
  502. tr::lng_channels_limit_title(),
  503. std::move(text),
  504. "channels",
  505. { defaultLimit, current, premiumLimit, &st::premiumIconGroups },
  506. true);
  507. AddSubtitle(box->verticalLayout(), tr::lng_channels_leave_title());
  508. const auto delegate = box->lifetime().make_state<InactiveDelegate>();
  509. const auto controller = box->lifetime().make_state<InactiveController>(
  510. session);
  511. const auto content = box->addRow(
  512. object_ptr<PeerListContent>(box, controller),
  513. {});
  514. delegate->setContent(content);
  515. controller->setDelegate(delegate);
  516. const auto count = 100;
  517. const auto placeholder = box->addRow(
  518. object_ptr<PeerListDummy>(box, count, st::defaultPeerList),
  519. {});
  520. using namespace rpl::mappers;
  521. controller->countValue(
  522. ) | rpl::filter(_1 > 0) | rpl::start_with_next([=] {
  523. delete placeholder;
  524. }, placeholder->lifetime());
  525. delegate->selectedCountChanges(
  526. ) | rpl::start_with_next([=](int count) {
  527. const auto leave = [=](const base::flat_set<PeerListRowId> &ids) {
  528. for (const auto rowId : ids) {
  529. const auto id = peerToChannel(PeerId(rowId));
  530. if (const auto channel = session->data().channelLoaded(id)) {
  531. session->api().leaveChannel(channel);
  532. }
  533. }
  534. box->showToast(tr::lng_channels_leave_done(tr::now));
  535. box->closeBox();
  536. };
  537. box->clearButtons();
  538. if (count) {
  539. box->addButton(
  540. tr::lng_channels_leave(lt_count, rpl::single(count * 1.)),
  541. [=] { leave(delegate->selected()); });
  542. } else if (premium) {
  543. box->addButton(tr::lng_box_ok(), [=] {
  544. box->closeBox();
  545. });
  546. } else {
  547. box->addButton(tr::lng_limits_increase(), [=] {
  548. Settings::ShowPremium(session, LimitsPremiumRef("channels"));
  549. });
  550. }
  551. }, box->lifetime());
  552. }
  553. void PublicLinksLimitBox(
  554. not_null<Ui::GenericBox*> box,
  555. not_null<Window::SessionNavigation*> navigation,
  556. Fn<void()> retry) {
  557. const auto session = &navigation->session();
  558. const auto premium = session->premium();
  559. const auto premiumPossible = session->premiumPossible();
  560. const auto limits = Data::PremiumLimits(session);
  561. const auto defaultLimit = float64(limits.channelsPublicDefault());
  562. const auto premiumLimit = float64(limits.channelsPublicPremium());
  563. const auto current = (premium ? premiumLimit : defaultLimit);
  564. auto text = rpl::combine(
  565. tr::lng_links_limit1(
  566. lt_count,
  567. rpl::single(current),
  568. Ui::Text::RichLangValue),
  569. ((premium || !premiumPossible)
  570. ? tr::lng_links_limit2_final(Ui::Text::RichLangValue)
  571. : tr::lng_links_limit2(
  572. lt_count,
  573. rpl::single(premiumLimit),
  574. Ui::Text::RichLangValue))
  575. ) | rpl::map([](TextWithEntities &&a, TextWithEntities &&b) {
  576. return a.append(QChar(' ')).append(std::move(b));
  577. });
  578. SimpleLimitBox(
  579. box,
  580. nullptr,
  581. session,
  582. tr::lng_links_limit_title(),
  583. std::move(text),
  584. "channels_public",
  585. { defaultLimit, current, premiumLimit, &st::premiumIconLinks },
  586. true);
  587. AddSubtitle(box->verticalLayout(), tr::lng_links_revoke_title());
  588. const auto delegate = box->lifetime().make_state<InactiveDelegate>();
  589. const auto controller = box->lifetime().make_state<PublicsController>(
  590. navigation,
  591. crl::guard(box, [=] { box->closeBox(); retry(); }));
  592. const auto content = box->addRow(
  593. object_ptr<PeerListContent>(box, controller),
  594. {});
  595. delegate->setContent(content);
  596. controller->setDelegate(delegate);
  597. const auto count = defaultLimit;
  598. const auto placeholder = box->addRow(
  599. object_ptr<PeerListDummy>(box, count, st::defaultPeerList),
  600. {});
  601. using namespace rpl::mappers;
  602. controller->countValue(
  603. ) | rpl::filter(_1 > 0) | rpl::start_with_next([=] {
  604. delete placeholder;
  605. }, placeholder->lifetime());
  606. }
  607. void FilterChatsLimitBox(
  608. not_null<Ui::GenericBox*> box,
  609. not_null<Main::Session*> session,
  610. int currentCount,
  611. bool include) {
  612. const auto premium = session->premium();
  613. const auto premiumPossible = session->premiumPossible();
  614. const auto limits = Data::PremiumLimits(session);
  615. const auto defaultLimit = float64(limits.dialogFiltersChatsDefault());
  616. const auto premiumLimit = float64(limits.dialogFiltersChatsPremium());
  617. const auto current = std::clamp(
  618. float64(currentCount),
  619. defaultLimit,
  620. premiumLimit);
  621. auto text = rpl::combine(
  622. (include
  623. ? tr::lng_filter_chats_limit1
  624. : tr::lng_filter_chats_exlude_limit1)(
  625. lt_count,
  626. rpl::single(premium ? premiumLimit : defaultLimit),
  627. Ui::Text::RichLangValue),
  628. ((premium || !premiumPossible)
  629. ? rpl::single(TextWithEntities())
  630. : tr::lng_filter_chats_limit2(
  631. lt_count,
  632. rpl::single(premiumLimit),
  633. Ui::Text::RichLangValue))
  634. ) | rpl::map([](TextWithEntities &&a, TextWithEntities &&b) {
  635. return b.text.isEmpty()
  636. ? a
  637. : a.append(QChar(' ')).append(std::move(b));
  638. });
  639. SimpleLimitBox(
  640. box,
  641. nullptr,
  642. session,
  643. tr::lng_filter_chats_limit_title(),
  644. std::move(text),
  645. "dialog_filters_chats",
  646. { defaultLimit, current, premiumLimit, &st::premiumIconChats });
  647. }
  648. void FilterLinksLimitBox(
  649. not_null<Ui::GenericBox*> box,
  650. not_null<Main::Session*> session) {
  651. const auto premium = session->premium();
  652. const auto premiumPossible = session->premiumPossible();
  653. const auto limits = Data::PremiumLimits(session);
  654. const auto defaultLimit = float64(limits.dialogFiltersLinksDefault());
  655. const auto premiumLimit = float64(limits.dialogFiltersLinksPremium());
  656. const auto current = (premium ? premiumLimit : defaultLimit);
  657. auto text = rpl::combine(
  658. tr::lng_filter_links_limit1(
  659. lt_count,
  660. rpl::single(premium ? premiumLimit : defaultLimit),
  661. Ui::Text::RichLangValue),
  662. ((premium || !premiumPossible)
  663. ? rpl::single(TextWithEntities())
  664. : tr::lng_filter_links_limit2(
  665. lt_count,
  666. rpl::single(premiumLimit),
  667. Ui::Text::RichLangValue))
  668. ) | rpl::map([](TextWithEntities &&a, TextWithEntities &&b) {
  669. return b.text.isEmpty()
  670. ? a
  671. : a.append(QChar(' ')).append(std::move(b));
  672. });
  673. SimpleLimitBox(
  674. box,
  675. nullptr,
  676. session,
  677. tr::lng_filter_links_limit_title(),
  678. std::move(text),
  679. "chatlist_invites",
  680. {
  681. defaultLimit,
  682. current,
  683. premiumLimit,
  684. &st::premiumIconChats,
  685. std::nullopt,
  686. /*true */}); // Don't use real ratio, "Free" doesn't fit.
  687. }
  688. void FiltersLimitBox(
  689. not_null<Ui::GenericBox*> box,
  690. not_null<Main::Session*> session,
  691. std::optional<int> filtersCountOverride) {
  692. const auto premium = session->premium();
  693. const auto premiumPossible = session->premiumPossible();
  694. const auto limits = Data::PremiumLimits(session);
  695. const auto defaultLimit = float64(limits.dialogFiltersDefault());
  696. const auto premiumLimit = float64(limits.dialogFiltersPremium());
  697. const auto cloud = int(ranges::count_if(
  698. session->data().chatsFilters().list(),
  699. [](const Data::ChatFilter &f) { return f.id() != FilterId(); }));
  700. const auto current = float64(filtersCountOverride.value_or(cloud));
  701. auto text = rpl::combine(
  702. tr::lng_filters_limit1(
  703. lt_count,
  704. rpl::single(premium ? premiumLimit : defaultLimit),
  705. Ui::Text::RichLangValue),
  706. ((premium || !premiumPossible)
  707. ? rpl::single(TextWithEntities())
  708. : tr::lng_filters_limit2(
  709. lt_count,
  710. rpl::single(premiumLimit),
  711. Ui::Text::RichLangValue))
  712. ) | rpl::map([](TextWithEntities &&a, TextWithEntities &&b) {
  713. return b.text.isEmpty()
  714. ? a
  715. : a.append(QChar(' ')).append(std::move(b));
  716. });
  717. SimpleLimitBox(
  718. box,
  719. nullptr,
  720. session,
  721. tr::lng_filters_limit_title(),
  722. std::move(text),
  723. "dialog_filters",
  724. { defaultLimit, current, premiumLimit, &st::premiumIconFolders });
  725. }
  726. void ShareableFiltersLimitBox(
  727. not_null<Ui::GenericBox*> box,
  728. not_null<Main::Session*> session) {
  729. const auto premium = session->premium();
  730. const auto premiumPossible = session->premiumPossible();
  731. const auto limits = Data::PremiumLimits(session);
  732. const auto defaultLimit = float64(limits.dialogShareableFiltersDefault());
  733. const auto premiumLimit = float64(limits.dialogShareableFiltersPremium());
  734. const auto current = float64(ranges::count_if(
  735. session->data().chatsFilters().list(),
  736. [](const Data::ChatFilter &f) { return f.chatlist(); }));
  737. auto text = rpl::combine(
  738. tr::lng_filter_shared_limit1(
  739. lt_count,
  740. rpl::single(premium ? premiumLimit : defaultLimit),
  741. Ui::Text::RichLangValue),
  742. ((premium || !premiumPossible)
  743. ? rpl::single(TextWithEntities())
  744. : tr::lng_filter_shared_limit2(
  745. lt_count,
  746. rpl::single(premiumLimit),
  747. Ui::Text::RichLangValue))
  748. ) | rpl::map([](TextWithEntities &&a, TextWithEntities &&b) {
  749. return b.text.isEmpty()
  750. ? a
  751. : a.append(QChar(' ')).append(std::move(b));
  752. });
  753. SimpleLimitBox(
  754. box,
  755. nullptr,
  756. session,
  757. tr::lng_filter_shared_limit_title(),
  758. std::move(text),
  759. "chatlists_joined",
  760. {
  761. defaultLimit,
  762. current,
  763. premiumLimit,
  764. &st::premiumIconFolders,
  765. std::nullopt,
  766. /*true*/ }); // Don't use real ratio, "Free" doesn't fit.
  767. }
  768. void FilterPinsLimitBox(
  769. not_null<Ui::GenericBox*> box,
  770. not_null<Main::Session*> session,
  771. FilterId filterId) {
  772. const auto limits = Data::PremiumLimits(session);
  773. SimplePinsLimitBox(
  774. box,
  775. session,
  776. "dialog_filters_pinned",
  777. limits.dialogFiltersChatsDefault(),
  778. limits.dialogFiltersChatsPremium(),
  779. PinsCount(session->data().chatsFilters().chatsList(filterId)));
  780. }
  781. void FolderPinsLimitBox(
  782. not_null<Ui::GenericBox*> box,
  783. not_null<Main::Session*> session) {
  784. const auto limits = Data::PremiumLimits(session);
  785. SimplePinsLimitBox(
  786. box,
  787. session,
  788. "dialogs_folder_pinned",
  789. limits.dialogsFolderPinnedDefault(),
  790. limits.dialogsFolderPinnedPremium(),
  791. PinsCount(session->data().folder(Data::Folder::kId)->chatsList()));
  792. }
  793. void PinsLimitBox(
  794. not_null<Ui::GenericBox*> box,
  795. not_null<Main::Session*> session) {
  796. const auto limits = Data::PremiumLimits(session);
  797. SimplePinsLimitBox(
  798. box,
  799. session,
  800. "dialog_pinned",
  801. limits.dialogsPinnedDefault(),
  802. limits.dialogsPinnedPremium(),
  803. PinsCount(session->data().chatsList()));
  804. }
  805. void SublistsPinsLimitBox(
  806. not_null<Ui::GenericBox*> box,
  807. not_null<Main::Session*> session) {
  808. const auto limits = Data::PremiumLimits(session);
  809. SimplePinsLimitBox(
  810. box,
  811. session,
  812. "saved_dialog_pinned",
  813. limits.savedSublistsPinnedDefault(),
  814. limits.savedSublistsPinnedPremium(),
  815. PinsCount(session->data().savedMessages().chatsList()));
  816. }
  817. void ForumPinsLimitBox(
  818. not_null<Ui::GenericBox*> box,
  819. not_null<Data::Forum*> forum) {
  820. const auto current = forum->owner().pinnedChatsLimit(forum) * 1.;
  821. auto text = tr::lng_forum_pin_limit(
  822. lt_count,
  823. rpl::single(current),
  824. Ui::Text::RichLangValue);
  825. SimpleLimitBox(
  826. box,
  827. nullptr,
  828. &forum->session(),
  829. false,
  830. tr::lng_filter_pin_limit_title(),
  831. std::move(text),
  832. QString(),
  833. { current, current, current * 2, &st::premiumIconPins });
  834. }
  835. void CaptionLimitBox(
  836. not_null<Ui::GenericBox*> box,
  837. not_null<Main::Session*> session,
  838. int remove,
  839. const style::PremiumLimits *stOverride) {
  840. const auto premium = session->premium();
  841. const auto premiumPossible = session->premiumPossible();
  842. const auto limits = Data::PremiumLimits(session);
  843. const auto defaultLimit = float64(limits.captionLengthDefault());
  844. const auto premiumLimit = float64(limits.captionLengthPremium());
  845. const auto currentLimit = premium ? premiumLimit : defaultLimit;
  846. const auto current = std::clamp(
  847. remove + currentLimit,
  848. defaultLimit,
  849. premiumLimit);
  850. auto text = rpl::combine(
  851. tr::lng_caption_limit1(
  852. lt_count,
  853. rpl::single(currentLimit),
  854. Ui::Text::RichLangValue),
  855. (!premiumPossible
  856. ? rpl::single(TextWithEntities())
  857. : tr::lng_caption_limit2(
  858. lt_count,
  859. rpl::single(premiumLimit),
  860. Ui::Text::RichLangValue))
  861. ) | rpl::map([](TextWithEntities &&a, TextWithEntities &&b) {
  862. return b.text.isEmpty()
  863. ? a
  864. : a.append(QChar(' ')).append(std::move(b));
  865. });
  866. SimpleLimitBox(
  867. box,
  868. stOverride,
  869. session,
  870. tr::lng_caption_limit_title(),
  871. std::move(text),
  872. "caption_length",
  873. { defaultLimit, current, premiumLimit, &st::premiumIconChats });
  874. }
  875. void CaptionLimitReachedBox(
  876. not_null<Ui::GenericBox*> box,
  877. not_null<Main::Session*> session,
  878. int remove,
  879. const style::PremiumLimits *stOverride) {
  880. Ui::ConfirmBox(box, Ui::ConfirmBoxArgs{
  881. .text = tr::lng_caption_limit_reached(tr::now, lt_count, remove),
  882. .labelStyle = stOverride ? &stOverride->boxLabel : nullptr,
  883. .inform = true,
  884. });
  885. if (!session->premium()) {
  886. box->addLeftButton(tr::lng_limits_increase(), [=] {
  887. box->getDelegate()->showBox(
  888. Box(CaptionLimitBox, session, remove, stOverride),
  889. Ui::LayerOption::KeepOther,
  890. anim::type::normal);
  891. box->closeBox();
  892. });
  893. }
  894. }
  895. void FileSizeLimitBox(
  896. not_null<Ui::GenericBox*> box,
  897. not_null<Main::Session*> session,
  898. uint64 fileSizeBytes,
  899. const style::PremiumLimits *stOverride) {
  900. const auto limits = Data::PremiumLimits(session);
  901. const auto defaultLimit = float64(limits.uploadMaxDefault());
  902. const auto premiumLimit = float64(limits.uploadMaxPremium());
  903. const auto defaultGb = float64(int(defaultLimit + 999) / 2000);
  904. const auto premiumGb = float64(int(premiumLimit + 999) / 2000);
  905. const auto tooLarge = (fileSizeBytes > premiumLimit * 512ULL * 1024);
  906. const auto showLimit = tooLarge ? premiumGb : defaultGb;
  907. const auto premiumPossible = !tooLarge && session->premiumPossible();
  908. const auto current = (fileSizeBytes && premiumPossible)
  909. ? std::clamp(
  910. float64(((fileSizeBytes / uint64(1024 * 1024)) + 499) / 1000),
  911. defaultGb,
  912. premiumGb)
  913. : showLimit;
  914. const auto gb = [](int count) {
  915. return tr::lng_file_size_limit(tr::now, lt_count, count);
  916. };
  917. auto text = rpl::combine(
  918. tr::lng_file_size_limit1(
  919. lt_size,
  920. rpl::single(Ui::Text::Bold(gb(showLimit))),
  921. Ui::Text::RichLangValue),
  922. (!premiumPossible
  923. ? rpl::single(TextWithEntities())
  924. : tr::lng_file_size_limit2(
  925. lt_size,
  926. rpl::single(Ui::Text::Bold(gb(premiumGb))),
  927. Ui::Text::RichLangValue))
  928. ) | rpl::map([](TextWithEntities &&a, TextWithEntities &&b) {
  929. return a.append(QChar(' ')).append(std::move(b));
  930. });
  931. SimpleLimitBox(
  932. box,
  933. stOverride,
  934. session,
  935. premiumPossible,
  936. tr::lng_file_size_limit_title(),
  937. std::move(text),
  938. "upload_max_fileparts",
  939. {
  940. defaultGb,
  941. current,
  942. (tooLarge ? showLimit * 2 : premiumGb),
  943. &st::premiumIconFiles,
  944. tr::lng_file_size_limit
  945. });
  946. }
  947. void AccountsLimitBox(
  948. not_null<Ui::GenericBox*> box,
  949. not_null<Main::Session*> session) {
  950. const auto defaultLimit = Main::Domain::kMaxAccounts;
  951. const auto premiumLimit = Main::Domain::kPremiumMaxAccounts;
  952. using Args = Ui::Premium::AccountsRowArgs;
  953. const auto accounts = session->domain().orderedAccounts();
  954. auto promotePossible = ranges::views::all(
  955. accounts
  956. ) | ranges::views::filter([&](not_null<Main::Account*> account) {
  957. return account->sessionExists()
  958. && !account->session().premium()
  959. && account->session().premiumPossible();
  960. }) | ranges::views::transform([&](not_null<Main::Account*> account) {
  961. const auto user = account->session().user();
  962. return Args::Entry{ user->name(), PaintUserpicCallback(user, false)};
  963. }) | ranges::views::take(defaultLimit) | ranges::to_vector;
  964. const auto premiumPossible = !promotePossible.empty();
  965. const auto current = int(accounts.size());
  966. auto text = rpl::combine(
  967. tr::lng_accounts_limit1(
  968. lt_count,
  969. rpl::single<float64>(current),
  970. Ui::Text::RichLangValue),
  971. ((!premiumPossible || current > premiumLimit)
  972. ? rpl::single(TextWithEntities())
  973. : tr::lng_accounts_limit2(Ui::Text::RichLangValue))
  974. ) | rpl::map([](TextWithEntities &&a, TextWithEntities &&b) {
  975. return b.text.isEmpty()
  976. ? a
  977. : a.append(QChar(' ')).append(std::move(b));
  978. });
  979. box->setWidth(st::boxWideWidth);
  980. const auto top = box->verticalLayout();
  981. const auto group = std::make_shared<Ui::RadiobuttonGroup>(0);
  982. Ui::AddSkip(top, st::premiumInfographicPadding.top());
  983. Ui::Premium::AddBubbleRow(
  984. top,
  985. st::defaultPremiumBubble,
  986. BoxShowFinishes(box),
  987. 0,
  988. current,
  989. (!premiumPossible
  990. ? (current * 2)
  991. : (current > defaultLimit)
  992. ? (current + 1)
  993. : (defaultLimit * 2)),
  994. ChooseBubbleType(premiumPossible),
  995. std::nullopt,
  996. &st::premiumIconAccounts);
  997. Ui::AddSkip(top, st::premiumLineTextSkip);
  998. if (premiumPossible) {
  999. Ui::Premium::AddLimitRow(
  1000. top,
  1001. st::defaultPremiumLimits,
  1002. (QString::number(std::max(current, defaultLimit) + 1)
  1003. + ((current + 1 == premiumLimit) ? "" : "+")),
  1004. QString::number(defaultLimit));
  1005. Ui::AddSkip(top, st::premiumInfographicPadding.bottom());
  1006. }
  1007. box->setTitle(tr::lng_accounts_limit_title());
  1008. auto padding = st::boxPadding;
  1009. padding.setTop(padding.bottom());
  1010. top->add(
  1011. object_ptr<Ui::FlatLabel>(
  1012. box,
  1013. std::move(text),
  1014. st::aboutRevokePublicLabel),
  1015. padding);
  1016. if (!premiumPossible || current > premiumLimit) {
  1017. box->addButton(tr::lng_box_ok(), [=] {
  1018. box->closeBox();
  1019. });
  1020. return;
  1021. }
  1022. auto switchingLifetime = std::make_shared<rpl::lifetime>();
  1023. box->addButton(tr::lng_continue(), [=]() mutable {
  1024. const auto ref = QString();
  1025. const auto wasAccount = &session->account();
  1026. const auto nowAccount = accounts[group->current()];
  1027. if (wasAccount == nowAccount) {
  1028. Settings::ShowPremium(session, ref);
  1029. return;
  1030. }
  1031. if (*switchingLifetime) {
  1032. return;
  1033. }
  1034. *switchingLifetime = session->domain().activeSessionChanges(
  1035. ) | rpl::start_with_next([=](Main::Session *session) mutable {
  1036. if (session) {
  1037. Settings::ShowPremium(session, ref);
  1038. }
  1039. if (switchingLifetime) {
  1040. base::take(switchingLifetime)->destroy();
  1041. }
  1042. });
  1043. session->domain().activate(nowAccount);
  1044. });
  1045. box->addButton(tr::lng_cancel(), [=] {
  1046. box->closeBox();
  1047. });
  1048. auto args = Args{
  1049. .group = group,
  1050. .st = st::premiumAccountsCheckbox,
  1051. .stName = st::shareBoxListItem.nameStyle,
  1052. .stNameFg = st::shareBoxListItem.nameFg,
  1053. .entries = std::move(promotePossible),
  1054. };
  1055. if (!args.entries.empty()) {
  1056. box->addSkip(st::premiumAccountsPadding.top());
  1057. Ui::Premium::AddAccountsRow(box->verticalLayout(), std::move(args));
  1058. box->addSkip(st::premiumAccountsPadding.bottom());
  1059. }
  1060. }
  1061. QString LimitsPremiumRef(const QString &addition) {
  1062. return "double_limits__" + addition;
  1063. }