settings_recipients_helper.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  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/business/settings_recipients_helper.h"
  8. #include "boxes/filters/edit_filter_chats_list.h"
  9. #include "boxes/filters/edit_filter_chats_preview.h"
  10. #include "data/business/data_shortcut_messages.h"
  11. #include "data/data_session.h"
  12. #include "data/data_user.h"
  13. #include "history/history.h"
  14. #include "lang/lang_keys.h"
  15. #include "main/main_app_config.h"
  16. #include "main/main_session.h"
  17. #include "settings/settings_common.h"
  18. #include "ui/widgets/checkbox.h"
  19. #include "ui/wrap/slide_wrap.h"
  20. #include "ui/wrap/vertical_layout.h"
  21. #include "ui/vertical_list.h"
  22. #include "window/window_session_controller.h"
  23. #include "styles/style_settings.h"
  24. namespace Settings {
  25. namespace {
  26. constexpr auto kAllExcept = 0;
  27. constexpr auto kSelectedOnly = 1;
  28. using Flag = Data::ChatFilter::Flag;
  29. using Flags = Data::ChatFilter::Flags;
  30. [[nodiscard]] Flags TypesToFlags(Data::BusinessChatTypes types) {
  31. using Type = Data::BusinessChatType;
  32. return ((types & Type::Contacts) ? Flag::Contacts : Flag())
  33. | ((types & Type::NonContacts) ? Flag::NonContacts : Flag())
  34. | ((types & Type::NewChats) ? Flag::NewChats : Flag())
  35. | ((types & Type::ExistingChats) ? Flag::ExistingChats : Flag());
  36. }
  37. [[nodiscard]] Data::BusinessChatTypes FlagsToTypes(Flags flags) {
  38. using Type = Data::BusinessChatType;
  39. return ((flags & Flag::Contacts) ? Type::Contacts : Type())
  40. | ((flags & Flag::NonContacts) ? Type::NonContacts : Type())
  41. | ((flags & Flag::NewChats) ? Type::NewChats : Type())
  42. | ((flags & Flag::ExistingChats) ? Type::ExistingChats : Type());
  43. }
  44. } // namespace
  45. void EditBusinessChats(
  46. not_null<Window::SessionController*> window,
  47. BusinessChatsDescriptor &&descriptor) {
  48. const auto session = &window->session();
  49. const auto options = Flag::ExistingChats
  50. | Flag::NewChats
  51. | Flag::Contacts
  52. | Flag::NonContacts;
  53. auto &&peers = descriptor.current.list | ranges::views::transform([=](
  54. not_null<UserData*> user) {
  55. return user->owner().history(user);
  56. });
  57. auto controller = std::make_unique<EditFilterChatsListController>(
  58. session,
  59. (descriptor.include
  60. ? tr::lng_filters_include_title()
  61. : tr::lng_filters_exclude_title()),
  62. (descriptor.usersOnly ? Flag() : options),
  63. TypesToFlags(descriptor.current.types) & options,
  64. base::flat_set<not_null<History*>>(begin(peers), end(peers)),
  65. 100,
  66. nullptr);
  67. const auto rawController = controller.get();
  68. const auto save = descriptor.save;
  69. auto initBox = [=](not_null<PeerListBox*> box) {
  70. box->setCloseByOutsideClick(false);
  71. box->addButton(tr::lng_settings_save(), crl::guard(box, [=] {
  72. const auto peers = box->collectSelectedRows();
  73. auto &&users = ranges::views::all(
  74. peers
  75. ) | ranges::views::transform([=](not_null<PeerData*> peer) {
  76. return not_null(peer->asUser());
  77. }) | ranges::to_vector;
  78. save(Data::BusinessChats{
  79. .types = FlagsToTypes(rawController->chosenOptions()),
  80. .list = std::move(users),
  81. });
  82. box->closeBox();
  83. }));
  84. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  85. };
  86. window->show(
  87. Box<PeerListBox>(std::move(controller), std::move(initBox)));
  88. }
  89. not_null<FilterChatsPreview*> SetupBusinessChatsPreview(
  90. not_null<Ui::VerticalLayout*> container,
  91. not_null<rpl::variable<Data::BusinessChats>*> data) {
  92. const auto rules = data->current();
  93. const auto locked = std::make_shared<bool>();
  94. auto &&peers = data->current().list | ranges::views::transform([=](
  95. not_null<UserData*> user) {
  96. return user->owner().history(user);
  97. });
  98. const auto preview = container->add(object_ptr<FilterChatsPreview>(
  99. container,
  100. TypesToFlags(data->current().types),
  101. base::flat_set<not_null<History*>>(begin(peers), end(peers))));
  102. preview->flagRemoved(
  103. ) | rpl::start_with_next([=](Flag flag) {
  104. *locked = true;
  105. *data = Data::BusinessChats{
  106. data->current().types & ~FlagsToTypes(flag),
  107. data->current().list
  108. };
  109. *locked = false;
  110. }, preview->lifetime());
  111. preview->peerRemoved(
  112. ) | rpl::start_with_next([=](not_null<History*> history) {
  113. auto list = data->current().list;
  114. list.erase(
  115. ranges::remove(list, not_null(history->peer->asUser())),
  116. end(list));
  117. *locked = true;
  118. *data = Data::BusinessChats{
  119. data->current().types,
  120. std::move(list)
  121. };
  122. *locked = false;
  123. }, preview->lifetime());
  124. data->changes(
  125. ) | rpl::filter([=] {
  126. return !*locked;
  127. }) | rpl::start_with_next([=](const Data::BusinessChats &rules) {
  128. auto &&peers = rules.list | ranges::views::transform([=](
  129. not_null<UserData*> user) {
  130. return user->owner().history(user);
  131. });
  132. preview->updateData(
  133. TypesToFlags(rules.types),
  134. base::flat_set<not_null<History*>>(begin(peers), end(peers)));
  135. }, preview->lifetime());
  136. return preview;
  137. }
  138. void AddBusinessRecipientsSelector(
  139. not_null<Ui::VerticalLayout*> container,
  140. BusinessRecipientsSelectorDescriptor &&descriptor) {
  141. Ui::AddSkip(container);
  142. Ui::AddSubsectionTitle(container, std::move(descriptor.title));
  143. auto &lifetime = container->lifetime();
  144. const auto controller = descriptor.controller;
  145. const auto data = descriptor.data;
  146. const auto includeWithExcluded = (descriptor.type
  147. == Data::BusinessRecipientsType::Bots);
  148. const auto change = [=](Fn<void(Data::BusinessRecipients&)> modify) {
  149. auto now = data->current();
  150. modify(now);
  151. *data = std::move(now);
  152. };
  153. const auto &current = data->current();
  154. const auto all = current.allButExcluded || current.included.empty();
  155. const auto group = std::make_shared<Ui::RadiobuttonGroup>(
  156. all ? kAllExcept : kSelectedOnly);
  157. container->add(
  158. object_ptr<Ui::Radiobutton>(
  159. container,
  160. group,
  161. kAllExcept,
  162. tr::lng_chatbots_all_except(tr::now),
  163. st::settingsChatbotsAccess),
  164. st::settingsChatbotsAccessMargins);
  165. container->add(
  166. object_ptr<Ui::Radiobutton>(
  167. container,
  168. group,
  169. kSelectedOnly,
  170. tr::lng_chatbots_selected(tr::now),
  171. st::settingsChatbotsAccess),
  172. st::settingsChatbotsAccessMargins);
  173. Ui::AddSkip(container, st::settingsChatbotsAccessSkip);
  174. Ui::AddDivider(container);
  175. const auto includeWrap = container->add(
  176. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  177. container,
  178. object_ptr<Ui::VerticalLayout>(container))
  179. )->setDuration(0);
  180. const auto excludeWrap = container->add(
  181. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  182. container,
  183. object_ptr<Ui::VerticalLayout>(container))
  184. )->setDuration(0);
  185. const auto excludeInner = excludeWrap->entity();
  186. Ui::AddSkip(excludeInner);
  187. Ui::AddSubsectionTitle(excludeInner, tr::lng_chatbots_excluded_title());
  188. const auto excludeAdd = AddButtonWithIcon(
  189. excludeInner,
  190. tr::lng_chatbots_exclude_button(),
  191. st::settingsChatbotsAdd,
  192. { &st::settingsIconRemove, IconType::Round, &st::windowBgActive });
  193. const auto addExcluded = [=] {
  194. const auto save = [=](Data::BusinessChats value) {
  195. change([&](Data::BusinessRecipients &data) {
  196. if (includeWithExcluded) {
  197. if (!data.allButExcluded) {
  198. value.types = {};
  199. }
  200. for (const auto &user : value.list) {
  201. data.included.list.erase(
  202. ranges::remove(data.included.list, user),
  203. end(data.included.list));
  204. }
  205. }
  206. if (!value.empty()) {
  207. data.included = {};
  208. }
  209. data.excluded = std::move(value);
  210. });
  211. };
  212. EditBusinessChats(controller, {
  213. .current = data->current().excluded,
  214. .save = crl::guard(excludeAdd, save),
  215. .usersOnly = (includeWithExcluded
  216. && !data->current().allButExcluded),
  217. .include = false,
  218. });
  219. };
  220. excludeAdd->setClickedCallback(addExcluded);
  221. const auto excluded = lifetime.make_state<
  222. rpl::variable<Data::BusinessChats>
  223. >(data->current().excluded);
  224. data->changes(
  225. ) | rpl::start_with_next([=](const Data::BusinessRecipients &value) {
  226. *excluded = value.excluded;
  227. }, lifetime);
  228. excluded->changes(
  229. ) | rpl::start_with_next([=](Data::BusinessChats &&value) {
  230. change([&](Data::BusinessRecipients &data) {
  231. data.excluded = std::move(value);
  232. });
  233. }, lifetime);
  234. SetupBusinessChatsPreview(excludeInner, excluded);
  235. excludeWrap->toggleOn(data->value(
  236. ) | rpl::map([=](const Data::BusinessRecipients &value) {
  237. return value.allButExcluded || includeWithExcluded;
  238. }));
  239. excludeWrap->finishAnimating();
  240. const auto includeInner = includeWrap->entity();
  241. Ui::AddSkip(includeInner);
  242. Ui::AddSubsectionTitle(includeInner, tr::lng_chatbots_included_title());
  243. const auto includeAdd = AddButtonWithIcon(
  244. includeInner,
  245. tr::lng_chatbots_include_button(),
  246. st::settingsChatbotsAdd,
  247. { &st::settingsIconAdd, IconType::Round, &st::windowBgActive });
  248. const auto addIncluded = [=] {
  249. const auto save = [=](Data::BusinessChats value) {
  250. change([&](Data::BusinessRecipients &data) {
  251. if (includeWithExcluded) {
  252. for (const auto &user : value.list) {
  253. data.excluded.list.erase(
  254. ranges::remove(data.excluded.list, user),
  255. end(data.excluded.list));
  256. }
  257. }
  258. if (!value.empty()) {
  259. data.excluded.types = {};
  260. }
  261. data.included = std::move(value);
  262. });
  263. if (!data->current().included.empty()) {
  264. group->setValue(kSelectedOnly);
  265. }
  266. };
  267. EditBusinessChats(controller, {
  268. .current = data->current().included,
  269. .save = crl::guard(includeAdd, save),
  270. .include = true,
  271. });
  272. };
  273. includeAdd->setClickedCallback(addIncluded);
  274. const auto included = lifetime.make_state<
  275. rpl::variable<Data::BusinessChats>
  276. >(data->current().included);
  277. data->changes(
  278. ) | rpl::start_with_next([=](const Data::BusinessRecipients &value) {
  279. *included = value.included;
  280. }, lifetime);
  281. included->changes(
  282. ) | rpl::start_with_next([=](Data::BusinessChats &&value) {
  283. change([&](Data::BusinessRecipients &data) {
  284. data.included = std::move(value);
  285. });
  286. }, lifetime);
  287. SetupBusinessChatsPreview(includeInner, included);
  288. included->value(
  289. ) | rpl::start_with_next([=](const Data::BusinessChats &value) {
  290. if (value.empty() && group->current() == kSelectedOnly) {
  291. group->setValue(kAllExcept);
  292. }
  293. }, lifetime);
  294. includeWrap->toggleOn(data->value(
  295. ) | rpl::map([](const Data::BusinessRecipients &value) {
  296. return !value.allButExcluded;
  297. }));
  298. includeWrap->finishAnimating();
  299. group->setChangedCallback([=](int value) {
  300. if (value == kSelectedOnly && data->current().included.empty()) {
  301. group->setValue(kAllExcept);
  302. addIncluded();
  303. return;
  304. }
  305. change([&](Data::BusinessRecipients &data) {
  306. data.allButExcluded = (value == kAllExcept);
  307. });
  308. });
  309. }
  310. int ShortcutsCount(not_null<Main::Session*> session) {
  311. const auto &shortcuts = session->data().shortcutMessages().shortcuts();
  312. auto result = 0;
  313. for (const auto &[_, shortcut] : shortcuts.list) {
  314. if (shortcut.count > 0) {
  315. ++result;
  316. }
  317. }
  318. return result;
  319. }
  320. rpl::producer<int> ShortcutsCountValue(not_null<Main::Session*> session) {
  321. const auto messages = &session->data().shortcutMessages();
  322. return rpl::single(rpl::empty) | rpl::then(
  323. messages->shortcutsChanged()
  324. ) | rpl::map([=] {
  325. return ShortcutsCount(session);
  326. });
  327. }
  328. int ShortcutMessagesCount(
  329. not_null<Main::Session*> session,
  330. const QString &name) {
  331. const auto &shortcuts = session->data().shortcutMessages().shortcuts();
  332. for (const auto &[_, shortcut] : shortcuts.list) {
  333. if (shortcut.name == name) {
  334. return shortcut.count;
  335. }
  336. }
  337. return 0;
  338. }
  339. rpl::producer<int> ShortcutMessagesCountValue(
  340. not_null<Main::Session*> session,
  341. const QString &name) {
  342. const auto messages = &session->data().shortcutMessages();
  343. return rpl::single(rpl::empty) | rpl::then(
  344. messages->shortcutsChanged()
  345. ) | rpl::map([=] {
  346. return ShortcutMessagesCount(session, name);
  347. });
  348. }
  349. bool ShortcutExists(not_null<Main::Session*> session, const QString &name) {
  350. return ShortcutMessagesCount(session, name) > 0;
  351. }
  352. rpl::producer<bool> ShortcutExistsValue(
  353. not_null<Main::Session*> session,
  354. const QString &name) {
  355. return ShortcutMessagesCountValue(session, name)
  356. | rpl::map(rpl::mappers::_1 > 0);
  357. }
  358. int ShortcutsLimit(not_null<Main::Session*> session) {
  359. const auto appConfig = &session->appConfig();
  360. return appConfig->get<int>("quick_replies_limit", 100);
  361. }
  362. rpl::producer<int> ShortcutsLimitValue(not_null<Main::Session*> session) {
  363. const auto appConfig = &session->appConfig();
  364. return appConfig->value() | rpl::map([=] {
  365. return ShortcutsLimit(session);
  366. });
  367. }
  368. int ShortcutMessagesLimit(not_null<Main::Session*> session) {
  369. const auto appConfig = &session->appConfig();
  370. return appConfig->get<int>("quick_reply_messages_limit", 20);
  371. }
  372. rpl::producer<int> ShortcutMessagesLimitValue(
  373. not_null<Main::Session*> session) {
  374. const auto appConfig = &session->appConfig();
  375. return appConfig->value() | rpl::map([=] {
  376. return ShortcutMessagesLimit(session);
  377. });
  378. }
  379. BusinessShortcutId LookupShortcutId(
  380. not_null<Main::Session*> session,
  381. const QString &name) {
  382. const auto messages = &session->data().shortcutMessages();
  383. for (const auto &[id, shortcut] : messages->shortcuts().list) {
  384. if (shortcut.name == name) {
  385. return id;
  386. }
  387. }
  388. return {};
  389. }
  390. } // namespace Settings