settings_business.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  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 "settings/settings_business.h"
  8. #include "api/api_chat_links.h"
  9. #include "boxes/premium_preview_box.h"
  10. #include "core/click_handler_types.h"
  11. #include "core/ui_integration.h" // TextContext
  12. #include "data/business/data_business_info.h"
  13. #include "data/business/data_business_chatbots.h"
  14. #include "data/business/data_shortcut_messages.h"
  15. #include "data/stickers/data_custom_emoji.h"
  16. #include "data/data_changes.h"
  17. #include "data/data_peer_values.h" // AmPremiumValue.
  18. #include "data/data_session.h"
  19. #include "data/data_user.h"
  20. #include "info/info_wrap_widget.h" // Info::Wrap.
  21. #include "info/settings/info_settings_widget.h" // SectionCustomTopBarData.
  22. #include "lang/lang_keys.h"
  23. #include "main/main_app_config.h"
  24. #include "main/main_session.h"
  25. #include "settings/business/settings_away_message.h"
  26. #include "settings/business/settings_chat_intro.h"
  27. #include "settings/business/settings_chat_links.h"
  28. #include "settings/business/settings_chatbots.h"
  29. #include "settings/business/settings_greeting.h"
  30. #include "settings/business/settings_location.h"
  31. #include "settings/business/settings_quick_replies.h"
  32. #include "settings/business/settings_working_hours.h"
  33. #include "settings/settings_common_session.h"
  34. #include "settings/settings_premium.h"
  35. #include "ui/effects/gradient.h"
  36. #include "ui/effects/premium_graphics.h"
  37. #include "ui/effects/premium_top_bar.h"
  38. #include "ui/layers/generic_box.h"
  39. #include "ui/text/text_utilities.h"
  40. #include "ui/widgets/checkbox.h" // Ui::RadiobuttonGroup.
  41. #include "ui/widgets/gradient_round_button.h"
  42. #include "ui/widgets/label_with_custom_emoji.h"
  43. #include "ui/wrap/fade_wrap.h"
  44. #include "ui/wrap/slide_wrap.h"
  45. #include "ui/wrap/vertical_layout.h"
  46. #include "ui/new_badges.h"
  47. #include "ui/vertical_list.h"
  48. #include "window/window_session_controller.h"
  49. #include "apiwrap.h"
  50. #include "api/api_premium.h"
  51. #include "styles/style_premium.h"
  52. #include "styles/style_info.h"
  53. #include "styles/style_layers.h"
  54. #include "styles/style_settings.h"
  55. #include "styles/style_chat.h"
  56. #include "styles/style_channel_earn.h"
  57. namespace Settings {
  58. namespace {
  59. struct Entry {
  60. const style::icon *icon;
  61. rpl::producer<QString> title;
  62. rpl::producer<QString> description;
  63. PremiumFeature feature = PremiumFeature::BusinessLocation;
  64. bool newBadge = false;
  65. };
  66. using Order = std::vector<QString>;
  67. [[nodiscard]] Order FallbackOrder() {
  68. return Order{
  69. u"greeting_message"_q,
  70. u"away_message"_q,
  71. u"quick_replies"_q,
  72. u"business_hours"_q,
  73. u"business_location"_q,
  74. u"business_links"_q,
  75. u"business_intro"_q,
  76. u"business_bots"_q,
  77. u"folder_tags"_q,
  78. };
  79. }
  80. [[nodiscard]] base::flat_map<QString, Entry> EntryMap() {
  81. return base::flat_map<QString, Entry>{
  82. {
  83. u"business_location"_q,
  84. Entry{
  85. &st::settingsBusinessIconLocation,
  86. tr::lng_business_subtitle_location(),
  87. tr::lng_business_about_location(),
  88. PremiumFeature::BusinessLocation,
  89. },
  90. },
  91. {
  92. u"business_hours"_q,
  93. Entry{
  94. &st::settingsBusinessIconHours,
  95. tr::lng_business_subtitle_opening_hours(),
  96. tr::lng_business_about_opening_hours(),
  97. PremiumFeature::BusinessHours,
  98. },
  99. },
  100. {
  101. u"quick_replies"_q,
  102. Entry{
  103. &st::settingsBusinessIconReplies,
  104. tr::lng_business_subtitle_quick_replies(),
  105. tr::lng_business_about_quick_replies(),
  106. PremiumFeature::QuickReplies,
  107. },
  108. },
  109. {
  110. u"greeting_message"_q,
  111. Entry{
  112. &st::settingsBusinessIconGreeting,
  113. tr::lng_business_subtitle_greeting_messages(),
  114. tr::lng_business_about_greeting_messages(),
  115. PremiumFeature::GreetingMessage,
  116. },
  117. },
  118. {
  119. u"away_message"_q,
  120. Entry{
  121. &st::settingsBusinessIconAway,
  122. tr::lng_business_subtitle_away_messages(),
  123. tr::lng_business_about_away_messages(),
  124. PremiumFeature::AwayMessage,
  125. },
  126. },
  127. {
  128. u"business_bots"_q,
  129. Entry{
  130. &st::settingsBusinessIconChatbots,
  131. tr::lng_business_subtitle_chatbots(),
  132. tr::lng_business_about_chatbots(),
  133. PremiumFeature::BusinessBots,
  134. true,
  135. },
  136. },
  137. {
  138. u"business_intro"_q,
  139. Entry{
  140. &st::settingsBusinessIconChatIntro,
  141. tr::lng_business_subtitle_chat_intro(),
  142. tr::lng_business_about_chat_intro(),
  143. PremiumFeature::ChatIntro,
  144. true,
  145. },
  146. },
  147. {
  148. u"business_links"_q,
  149. Entry{
  150. &st::settingsBusinessIconChatLinks,
  151. tr::lng_business_subtitle_chat_links(),
  152. tr::lng_business_about_chat_links(),
  153. PremiumFeature::ChatLinks,
  154. true,
  155. },
  156. },
  157. {
  158. u"folder_tags"_q,
  159. Entry{
  160. &st::settingsPremiumIconTags,
  161. tr::lng_premium_summary_subtitle_filter_tags(),
  162. tr::lng_premium_summary_about_filter_tags(),
  163. PremiumFeature::FilterTags,
  164. true,
  165. },
  166. },
  167. };
  168. }
  169. void AddBusinessSummary(
  170. not_null<Ui::VerticalLayout*> content,
  171. not_null<Window::SessionController*> controller,
  172. Fn<void(PremiumFeature)> buttonCallback) {
  173. const auto &stDefault = st::settingsButton;
  174. const auto &stLabel = st::defaultFlatLabel;
  175. const auto iconSize = st::settingsPremiumIconDouble.size();
  176. const auto &titlePadding = st::settingsPremiumRowTitlePadding;
  177. const auto &descriptionPadding = st::settingsPremiumRowAboutPadding;
  178. auto entryMap = EntryMap();
  179. auto iconContainers = std::vector<Ui::AbstractButton*>();
  180. iconContainers.reserve(int(entryMap.size()));
  181. const auto addRow = [&](Entry &entry) {
  182. const auto labelAscent = stLabel.style.font->ascent;
  183. const auto button = Ui::CreateChild<Ui::SettingsButton>(
  184. content.get(),
  185. rpl::single(QString()));
  186. const auto label = content->add(
  187. object_ptr<Ui::FlatLabel>(
  188. content,
  189. std::move(entry.title) | Ui::Text::ToBold(),
  190. stLabel),
  191. titlePadding);
  192. label->setAttribute(Qt::WA_TransparentForMouseEvents);
  193. const auto description = content->add(
  194. object_ptr<Ui::FlatLabel>(
  195. content,
  196. std::move(entry.description),
  197. st::boxDividerLabel),
  198. descriptionPadding);
  199. description->setAttribute(Qt::WA_TransparentForMouseEvents);
  200. if (entry.newBadge) {
  201. Ui::NewBadge::AddAfterLabel(content, label);
  202. }
  203. const auto dummy = Ui::CreateChild<Ui::AbstractButton>(content.get());
  204. dummy->setAttribute(Qt::WA_TransparentForMouseEvents);
  205. content->sizeValue(
  206. ) | rpl::start_with_next([=](const QSize &s) {
  207. dummy->resize(s.width(), iconSize.height());
  208. }, dummy->lifetime());
  209. label->geometryValue(
  210. ) | rpl::start_with_next([=](const QRect &r) {
  211. dummy->moveToLeft(0, r.y() + (r.height() - labelAscent));
  212. }, dummy->lifetime());
  213. rpl::combine(
  214. content->widthValue(),
  215. label->heightValue(),
  216. description->heightValue()
  217. ) | rpl::start_with_next([=,
  218. topPadding = titlePadding,
  219. bottomPadding = descriptionPadding](
  220. int width,
  221. int topHeight,
  222. int bottomHeight) {
  223. button->resize(
  224. width,
  225. topPadding.top()
  226. + topHeight
  227. + topPadding.bottom()
  228. + bottomPadding.top()
  229. + bottomHeight
  230. + bottomPadding.bottom());
  231. }, button->lifetime());
  232. label->topValue(
  233. ) | rpl::start_with_next([=, padding = titlePadding.top()](int top) {
  234. button->moveToLeft(0, top - padding);
  235. }, button->lifetime());
  236. const auto arrow = Ui::CreateChild<Ui::IconButton>(
  237. button,
  238. st::backButton);
  239. arrow->setIconOverride(
  240. &st::settingsPremiumArrow,
  241. &st::settingsPremiumArrowOver);
  242. arrow->setAttribute(Qt::WA_TransparentForMouseEvents);
  243. button->sizeValue(
  244. ) | rpl::start_with_next([=](const QSize &s) {
  245. const auto &point = st::settingsPremiumArrowShift;
  246. arrow->moveToRight(
  247. -point.x(),
  248. point.y() + (s.height() - arrow->height()) / 2);
  249. }, arrow->lifetime());
  250. const auto feature = entry.feature;
  251. button->setClickedCallback([=] { buttonCallback(feature); });
  252. iconContainers.push_back(dummy);
  253. };
  254. auto icons = std::vector<const style::icon *>();
  255. icons.reserve(int(entryMap.size()));
  256. {
  257. const auto session = &controller->session();
  258. const auto mtpOrder = session->appConfig().get<Order>(
  259. "business_promo_order",
  260. FallbackOrder());
  261. const auto processEntry = [&](Entry &entry) {
  262. icons.push_back(entry.icon);
  263. addRow(entry);
  264. };
  265. for (const auto &key : mtpOrder) {
  266. auto it = entryMap.find(key);
  267. if (it == end(entryMap)) {
  268. continue;
  269. }
  270. processEntry(it->second);
  271. }
  272. }
  273. content->resizeToWidth(content->height());
  274. // Icons.
  275. Assert(iconContainers.size() > 2);
  276. const auto from = iconContainers.front()->y();
  277. const auto to = iconContainers.back()->y() + iconSize.height();
  278. auto gradient = QLinearGradient(0, 0, 0, to - from);
  279. gradient.setStops(Ui::Premium::FullHeightGradientStops());
  280. for (auto i = 0; i < int(icons.size()); i++) {
  281. const auto &iconContainer = iconContainers[i];
  282. const auto pointTop = iconContainer->y() - from;
  283. const auto pointBottom = pointTop + iconContainer->height();
  284. const auto ratioTop = pointTop / float64(to - from);
  285. const auto ratioBottom = pointBottom / float64(to - from);
  286. auto resultGradient = QLinearGradient(
  287. QPointF(),
  288. QPointF(0, pointBottom - pointTop));
  289. resultGradient.setColorAt(
  290. .0,
  291. anim::gradient_color_at(gradient, ratioTop));
  292. resultGradient.setColorAt(
  293. .1,
  294. anim::gradient_color_at(gradient, ratioBottom));
  295. const auto brush = QBrush(resultGradient);
  296. AddButtonIcon(
  297. iconContainer,
  298. stDefault,
  299. { .icon = icons[i], .backgroundBrush = brush });
  300. }
  301. Ui::AddSkip(content, descriptionPadding.bottom());
  302. }
  303. class Business : public Section<Business> {
  304. public:
  305. Business(
  306. QWidget *parent,
  307. not_null<Window::SessionController*> controller);
  308. [[nodiscard]] rpl::producer<QString> title() override;
  309. [[nodiscard]] QPointer<Ui::RpWidget> createPinnedToTop(
  310. not_null<QWidget*> parent) override;
  311. [[nodiscard]] QPointer<Ui::RpWidget> createPinnedToBottom(
  312. not_null<Ui::RpWidget*> parent) override;
  313. void showFinished() override;
  314. [[nodiscard]] bool hasFlexibleTopBar() const override;
  315. void setStepDataReference(std::any &data) override;
  316. [[nodiscard]] rpl::producer<> sectionShowBack() override final;
  317. private:
  318. void setupContent();
  319. const not_null<Window::SessionController*> _controller;
  320. QPointer<Ui::GradientButton> _subscribe;
  321. base::unique_qptr<Ui::FadeWrap<Ui::IconButton>> _back;
  322. base::unique_qptr<Ui::IconButton> _close;
  323. rpl::variable<bool> _backToggles;
  324. rpl::variable<Info::Wrap> _wrap;
  325. Fn<void(bool)> _setPaused;
  326. std::shared_ptr<Ui::RadiobuttonGroup> _radioGroup;
  327. rpl::event_stream<> _showBack;
  328. rpl::event_stream<> _showFinished;
  329. rpl::variable<QString> _buttonText;
  330. PremiumFeature _waitingToShow = PremiumFeature::Business;
  331. };
  332. Business::Business(
  333. QWidget *parent,
  334. not_null<Window::SessionController*> controller)
  335. : Section(parent)
  336. , _controller(controller)
  337. , _radioGroup(std::make_shared<Ui::RadiobuttonGroup>()) {
  338. setupContent();
  339. _controller->session().api().premium().reload();
  340. }
  341. rpl::producer<QString> Business::title() {
  342. return tr::lng_premium_summary_title();
  343. }
  344. bool Business::hasFlexibleTopBar() const {
  345. return true;
  346. }
  347. rpl::producer<> Business::sectionShowBack() {
  348. return _showBack.events();
  349. }
  350. void Business::setStepDataReference(std::any &data) {
  351. using namespace Info::Settings;
  352. const auto my = std::any_cast<SectionCustomTopBarData>(&data);
  353. if (my) {
  354. _backToggles = std::move(
  355. my->backButtonEnables
  356. ) | rpl::map_to(true);
  357. _wrap = std::move(my->wrapValue);
  358. }
  359. }
  360. void Business::setupContent() {
  361. const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
  362. const auto owner = &_controller->session().data();
  363. owner->chatbots().preload();
  364. owner->businessInfo().preload();
  365. owner->shortcutMessages().preloadShortcuts();
  366. owner->session().api().chatLinks().preload();
  367. Ui::AddSkip(content, st::settingsFromFileTop);
  368. const auto showFeature = [=](PremiumFeature feature) {
  369. if (feature == PremiumFeature::FilterTags) {
  370. ShowPremiumPreviewToBuy(_controller, feature);
  371. return;
  372. }
  373. showOther([&] {
  374. switch (feature) {
  375. case PremiumFeature::AwayMessage: return AwayMessageId();
  376. case PremiumFeature::BusinessHours: return WorkingHoursId();
  377. case PremiumFeature::BusinessLocation: return LocationId();
  378. case PremiumFeature::GreetingMessage: return GreetingId();
  379. case PremiumFeature::QuickReplies: return QuickRepliesId();
  380. case PremiumFeature::BusinessBots: return ChatbotsId();
  381. case PremiumFeature::ChatIntro: return ChatIntroId();
  382. case PremiumFeature::ChatLinks: return ChatLinksId();
  383. }
  384. Unexpected("Feature in showFeature.");
  385. }());
  386. };
  387. const auto isReady = [=](PremiumFeature feature) {
  388. switch (feature) {
  389. case PremiumFeature::AwayMessage:
  390. return owner->businessInfo().awaySettingsLoaded()
  391. && owner->shortcutMessages().shortcutsLoaded();
  392. case PremiumFeature::BusinessHours:
  393. return owner->session().user()->isFullLoaded()
  394. && owner->businessInfo().timezonesLoaded();
  395. case PremiumFeature::BusinessLocation:
  396. return owner->session().user()->isFullLoaded();
  397. case PremiumFeature::GreetingMessage:
  398. return owner->businessInfo().greetingSettingsLoaded()
  399. && owner->shortcutMessages().shortcutsLoaded();
  400. case PremiumFeature::QuickReplies:
  401. return owner->shortcutMessages().shortcutsLoaded();
  402. case PremiumFeature::BusinessBots:
  403. return owner->chatbots().loaded();
  404. case PremiumFeature::ChatIntro:
  405. return owner->session().user()->isFullLoaded();
  406. case PremiumFeature::ChatLinks:
  407. return owner->session().api().chatLinks().loaded();
  408. case PremiumFeature::FilterTags:
  409. return true;
  410. }
  411. Unexpected("Feature in isReady.");
  412. };
  413. const auto check = [=] {
  414. if (_waitingToShow != PremiumFeature::Business
  415. && isReady(_waitingToShow)) {
  416. showFeature(
  417. std::exchange(_waitingToShow, PremiumFeature::Business));
  418. }
  419. };
  420. rpl::merge(
  421. owner->businessInfo().awaySettingsChanged(),
  422. owner->businessInfo().greetingSettingsChanged(),
  423. owner->businessInfo().timezonesValue() | rpl::to_empty,
  424. owner->shortcutMessages().shortcutsChanged(),
  425. owner->chatbots().changes() | rpl::to_empty,
  426. owner->session().changes().peerUpdates(
  427. owner->session().user(),
  428. Data::PeerUpdate::Flag::FullInfo) | rpl::to_empty,
  429. owner->session().api().chatLinks().loadedUpdates()
  430. ) | rpl::start_with_next(check, content->lifetime());
  431. AddBusinessSummary(content, _controller, [=](PremiumFeature feature) {
  432. if (!_controller->session().premium()) {
  433. _setPaused(true);
  434. const auto hidden = crl::guard(this, [=] { _setPaused(false); });
  435. ShowPremiumPreviewToBuy(_controller, feature, hidden);
  436. return;
  437. } else if (!isReady(feature)) {
  438. _waitingToShow = feature;
  439. } else {
  440. showFeature(feature);
  441. }
  442. });
  443. const auto sponsoredWrap = content->add(
  444. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  445. content,
  446. object_ptr<Ui::VerticalLayout>(content)));
  447. const auto fillSponsoredWrap = [=] {
  448. while (sponsoredWrap->entity()->count()) {
  449. delete sponsoredWrap->entity()->widgetAt(0);
  450. }
  451. Ui::AddDivider(sponsoredWrap->entity());
  452. const auto loading = sponsoredWrap->entity()->add(
  453. object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
  454. sponsoredWrap->entity(),
  455. object_ptr<Ui::FlatLabel>(
  456. sponsoredWrap->entity(),
  457. tr::lng_contacts_loading())),
  458. st::boxRowPadding);
  459. loading->entity()->setTextColorOverride(st::windowSubTextFg->c);
  460. const auto wrap = sponsoredWrap->entity()->add(
  461. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  462. sponsoredWrap->entity(),
  463. object_ptr<Ui::VerticalLayout>(sponsoredWrap->entity())));
  464. wrap->toggle(false, anim::type::instant);
  465. const auto inner = wrap->entity();
  466. Ui::AddSkip(inner);
  467. Ui::AddSubsectionTitle(
  468. inner,
  469. tr::lng_business_subtitle_sponsored());
  470. const auto button = inner->add(object_ptr<Ui::SettingsButton>(
  471. inner,
  472. tr::lng_business_button_sponsored()));
  473. Ui::AddSkip(inner);
  474. const auto session = &_controller->session();
  475. {
  476. const auto arrow = Ui::Text::IconEmoji(&st::textMoreIconEmoji);
  477. inner->add(object_ptr<Ui::DividerLabel>(
  478. inner,
  479. Ui::CreateLabelWithCustomEmoji(
  480. inner,
  481. tr::lng_business_about_sponsored(
  482. lt_link,
  483. rpl::combine(
  484. tr::lng_business_about_sponsored_link(
  485. lt_emoji,
  486. rpl::single(arrow),
  487. Ui::Text::RichLangValue),
  488. tr::lng_business_about_sponsored_url()
  489. ) | rpl::map([](TextWithEntities text, QString url) {
  490. return Ui::Text::Link(text, url);
  491. }),
  492. Ui::Text::RichLangValue),
  493. Core::TextContext({ .session = session }),
  494. st::boxDividerLabel),
  495. st::defaultBoxDividerLabelPadding,
  496. RectPart::Top | RectPart::Bottom));
  497. }
  498. const auto api = inner->lifetime().make_state<Api::SponsoredToggle>(
  499. session);
  500. api->toggled(
  501. ) | rpl::start_with_next([=](bool enabled) {
  502. button->toggleOn(rpl::single(enabled));
  503. wrap->toggle(true, anim::type::instant);
  504. loading->toggle(false, anim::type::instant);
  505. button->toggledChanges(
  506. ) | rpl::start_with_next([=](bool toggled) {
  507. api->setToggled(
  508. toggled
  509. ) | rpl::start_with_error_done([=](const QString &error) {
  510. _controller->showToast(error);
  511. }, [] {
  512. }, button->lifetime());
  513. }, button->lifetime());
  514. }, inner->lifetime());
  515. Ui::ToggleChildrenVisibility(sponsoredWrap->entity(), true);
  516. sponsoredWrap->entity()->resizeToWidth(content->width());
  517. };
  518. Data::AmPremiumValue(
  519. &_controller->session()
  520. ) | rpl::start_with_next([=](bool isPremium) {
  521. sponsoredWrap->toggle(isPremium, anim::type::normal);
  522. if (isPremium) {
  523. fillSponsoredWrap();
  524. }
  525. }, content->lifetime());
  526. Ui::ResizeFitChild(this, content);
  527. }
  528. QPointer<Ui::RpWidget> Business::createPinnedToTop(
  529. not_null<QWidget*> parent) {
  530. auto title = tr::lng_business_title();
  531. auto about = [&]() -> rpl::producer<TextWithEntities> {
  532. return rpl::conditional(
  533. Data::AmPremiumValue(&_controller->session()),
  534. tr::lng_business_unlocked(),
  535. tr::lng_business_about()
  536. ) | Ui::Text::ToWithEntities();
  537. }();
  538. const auto content = [&]() -> Ui::Premium::TopBarAbstract* {
  539. const auto weak = base::make_weak(_controller);
  540. const auto clickContextOther = [=] {
  541. return QVariant::fromValue(ClickHandlerContext{
  542. .sessionWindow = weak,
  543. .botStartAutoSubmit = true,
  544. });
  545. };
  546. return Ui::CreateChild<Ui::Premium::TopBar>(
  547. parent.get(),
  548. st::defaultPremiumCover,
  549. Ui::Premium::TopBarDescriptor{
  550. .clickContextOther = clickContextOther,
  551. .logo = u"dollar"_q,
  552. .title = std::move(title),
  553. .about = std::move(about),
  554. });
  555. }();
  556. _setPaused = [=](bool paused) {
  557. content->setPaused(paused);
  558. if (_subscribe) {
  559. _subscribe->setGlarePaused(paused);
  560. }
  561. };
  562. _wrap.value(
  563. ) | rpl::start_with_next([=](Info::Wrap wrap) {
  564. content->setRoundEdges(wrap == Info::Wrap::Layer);
  565. }, content->lifetime());
  566. content->setMaximumHeight(st::settingsPremiumTopHeight);
  567. content->setMinimumHeight(st::settingsPremiumTopHeight);
  568. content->resize(content->width(), content->maximumHeight());
  569. _wrap.value(
  570. ) | rpl::start_with_next([=](Info::Wrap wrap) {
  571. const auto isLayer = (wrap == Info::Wrap::Layer);
  572. _back = base::make_unique_q<Ui::FadeWrap<Ui::IconButton>>(
  573. content,
  574. object_ptr<Ui::IconButton>(
  575. content,
  576. (isLayer
  577. ? st::settingsPremiumLayerTopBarBack
  578. : st::settingsPremiumTopBarBack)),
  579. st::infoTopBarScale);
  580. _back->setDuration(0);
  581. _back->toggleOn(isLayer
  582. ? _backToggles.value() | rpl::type_erased()
  583. : rpl::single(true));
  584. _back->entity()->addClickHandler([=] {
  585. _showBack.fire({});
  586. });
  587. _back->toggledValue(
  588. ) | rpl::start_with_next([=](bool toggled) {
  589. const auto &st = isLayer ? st::infoLayerTopBar : st::infoTopBar;
  590. content->setTextPosition(
  591. toggled ? st.back.width : st.titlePosition.x(),
  592. st.titlePosition.y());
  593. }, _back->lifetime());
  594. if (!isLayer) {
  595. _close = nullptr;
  596. } else {
  597. _close = base::make_unique_q<Ui::IconButton>(
  598. content,
  599. st::settingsPremiumTopBarClose);
  600. _close->addClickHandler([=] {
  601. _controller->parentController()->hideLayer();
  602. _controller->parentController()->hideSpecialLayer();
  603. });
  604. content->widthValue(
  605. ) | rpl::start_with_next([=] {
  606. _close->moveToRight(0, 0);
  607. }, _close->lifetime());
  608. }
  609. }, content->lifetime());
  610. return Ui::MakeWeak(not_null<Ui::RpWidget*>{ content });
  611. }
  612. void Business::showFinished() {
  613. _showFinished.fire({});
  614. }
  615. QPointer<Ui::RpWidget> Business::createPinnedToBottom(
  616. not_null<Ui::RpWidget*> parent) {
  617. const auto content = Ui::CreateChild<Ui::RpWidget>(parent.get());
  618. const auto session = &_controller->session();
  619. auto buttonText = _buttonText.value();
  620. _subscribe = CreateSubscribeButton({
  621. _controller,
  622. content,
  623. [] { return u"business"_q; },
  624. std::move(buttonText),
  625. std::nullopt,
  626. [=, options = session->api().premium().subscriptionOptions()] {
  627. const auto value = _radioGroup->current();
  628. return (value < options.size() && value >= 0)
  629. ? options[value].botUrl
  630. : QString();
  631. },
  632. });
  633. {
  634. const auto callback = [=](int value) {
  635. auto &api = _controller->session().api();
  636. const auto options = api.premium().subscriptionOptions();
  637. if (options.empty()) {
  638. return;
  639. }
  640. Assert(value < options.size() && value >= 0);
  641. auto text = tr::lng_premium_subscribe_button(
  642. tr::now,
  643. lt_cost,
  644. options[value].costPerMonth);
  645. _buttonText = std::move(text);
  646. };
  647. _radioGroup->setChangedCallback(callback);
  648. callback(0);
  649. }
  650. _showFinished.events(
  651. ) | rpl::take(1) | rpl::start_with_next([=] {
  652. _subscribe->startGlareAnimation();
  653. }, _subscribe->lifetime());
  654. content->widthValue(
  655. ) | rpl::start_with_next([=](int width) {
  656. const auto padding = st::settingsPremiumButtonPadding;
  657. _subscribe->resizeToWidth(width - padding.left() - padding.right());
  658. }, _subscribe->lifetime());
  659. rpl::combine(
  660. _subscribe->heightValue(),
  661. Data::AmPremiumValue(session),
  662. session->premiumPossibleValue()
  663. ) | rpl::start_with_next([=](
  664. int buttonHeight,
  665. bool premium,
  666. bool premiumPossible) {
  667. const auto padding = st::settingsPremiumButtonPadding;
  668. const auto finalHeight = !premiumPossible
  669. ? 0
  670. : !premium
  671. ? (padding.top() + buttonHeight + padding.bottom())
  672. : 0;
  673. content->resize(content->width(), finalHeight);
  674. _subscribe->moveToLeft(padding.left(), padding.top());
  675. _subscribe->setVisible(!premium && premiumPossible);
  676. }, _subscribe->lifetime());
  677. return Ui::MakeWeak(not_null<Ui::RpWidget*>{ content });
  678. }
  679. } // namespace
  680. template <>
  681. struct SectionFactory<Business> : AbstractSectionFactory {
  682. object_ptr<AbstractSection> create(
  683. not_null<QWidget*> parent,
  684. not_null<Window::SessionController*> controller,
  685. not_null<Ui::ScrollArea*> scroll,
  686. rpl::producer<Container> containerValue
  687. ) const final override {
  688. return object_ptr<Business>(parent, controller);
  689. }
  690. bool hasCustomTopBar() const final override {
  691. return true;
  692. }
  693. [[nodiscard]] static const std::shared_ptr<SectionFactory> &Instance() {
  694. static const auto result = std::make_shared<SectionFactory>();
  695. return result;
  696. }
  697. };
  698. Type BusinessId() {
  699. return Business::Id();
  700. }
  701. void ShowBusiness(not_null<Window::SessionController*> controller) {
  702. if (!controller->session().premiumPossible()) {
  703. controller->show(Box(PremiumUnavailableBox));
  704. return;
  705. }
  706. controller->showSettings(Settings::BusinessId());
  707. }
  708. std::vector<PremiumFeature> BusinessFeaturesOrder(
  709. not_null<::Main::Session*> session) {
  710. const auto mtpOrder = session->appConfig().get<Order>(
  711. "business_promo_order",
  712. FallbackOrder());
  713. return ranges::views::all(
  714. mtpOrder
  715. ) | ranges::views::transform([](const QString &s) {
  716. if (s == u"greeting_message"_q) {
  717. return PremiumFeature::GreetingMessage;
  718. } else if (s == u"away_message"_q) {
  719. return PremiumFeature::AwayMessage;
  720. } else if (s == u"quick_replies"_q) {
  721. return PremiumFeature::QuickReplies;
  722. } else if (s == u"business_hours"_q) {
  723. return PremiumFeature::BusinessHours;
  724. } else if (s == u"business_location"_q) {
  725. return PremiumFeature::BusinessLocation;
  726. } else if (s == u"business_links"_q) {
  727. return PremiumFeature::ChatLinks;
  728. } else if (s == u"business_intro"_q) {
  729. return PremiumFeature::ChatIntro;
  730. } else if (s == u"business_bots"_q) {
  731. return PremiumFeature::BusinessBots;
  732. } else if (s == u"folder_tags"_q) {
  733. return PremiumFeature::FilterTags;
  734. }
  735. return PremiumFeature::kCount;
  736. }) | ranges::views::filter([](PremiumFeature feature) {
  737. return (feature != PremiumFeature::kCount);
  738. }) | ranges::to_vector;
  739. }
  740. } // namespace Settings