settings_quick_replies.cpp 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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_quick_replies.h"
  8. #include "boxes/premium_preview_box.h"
  9. #include "core/application.h"
  10. #include "data/business/data_shortcut_messages.h"
  11. #include "data/data_session.h"
  12. #include "lang/lang_keys.h"
  13. #include "main/main_account.h"
  14. #include "main/main_session.h"
  15. #include "settings/business/settings_recipients_helper.h"
  16. #include "settings/business/settings_shortcut_messages.h"
  17. #include "ui/layers/generic_box.h"
  18. #include "ui/text/text_utilities.h"
  19. #include "ui/widgets/buttons.h"
  20. #include "ui/widgets/fields/input_field.h"
  21. #include "ui/wrap/slide_wrap.h"
  22. #include "ui/wrap/vertical_layout.h"
  23. #include "ui/vertical_list.h"
  24. #include "window/window_session_controller.h"
  25. #include "styles/style_chat_helpers.h"
  26. #include "styles/style_layers.h"
  27. #include "styles/style_settings.h"
  28. namespace Settings {
  29. namespace {
  30. constexpr auto kShortcutLimit = 32;
  31. class QuickReplies : public BusinessSection<QuickReplies> {
  32. public:
  33. QuickReplies(
  34. QWidget *parent,
  35. not_null<Window::SessionController*> controller);
  36. ~QuickReplies();
  37. [[nodiscard]] rpl::producer<QString> title() override;
  38. private:
  39. void setupContent(not_null<Window::SessionController*> controller);
  40. rpl::variable<int> _count;
  41. };
  42. QuickReplies::QuickReplies(
  43. QWidget *parent,
  44. not_null<Window::SessionController*> controller)
  45. : BusinessSection(parent, controller) {
  46. setupContent(controller);
  47. }
  48. QuickReplies::~QuickReplies() = default;
  49. rpl::producer<QString> QuickReplies::title() {
  50. return tr::lng_replies_title();
  51. }
  52. void QuickReplies::setupContent(
  53. not_null<Window::SessionController*> controller) {
  54. using namespace rpl::mappers;
  55. const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
  56. AddDividerTextWithLottie(content, {
  57. .lottie = u"writing"_q,
  58. .lottieSize = st::settingsCloudPasswordIconSize,
  59. .lottieMargins = st::peerAppearanceIconPadding,
  60. .showFinished = showFinishes(),
  61. .about = tr::lng_replies_about(Ui::Text::WithEntities),
  62. .aboutMargins = st::peerAppearanceCoverLabelMargin,
  63. });
  64. Ui::AddSkip(content);
  65. const auto addWrap = content->add(
  66. object_ptr<Ui::VerticalLayout>(content));
  67. const auto owner = &controller->session().data();
  68. const auto messages = &owner->shortcutMessages();
  69. rpl::combine(
  70. _count.value(),
  71. ShortcutsLimitValue(&controller->session())
  72. ) | rpl::start_with_next([=](int count, int limit) {
  73. while (addWrap->count()) {
  74. delete addWrap->widgetAt(0);
  75. }
  76. if (count < limit) {
  77. const auto add = addWrap->add(object_ptr<Ui::SettingsButton>(
  78. addWrap,
  79. tr::lng_replies_add(),
  80. st::settingsButtonNoIcon
  81. ));
  82. add->setClickedCallback([=] {
  83. if (!controller->session().premium()) {
  84. ShowPremiumPreviewToBuy(
  85. controller,
  86. PremiumFeature::QuickReplies);
  87. return;
  88. }
  89. const auto submit = [=](QString name, Fn<void()> close) {
  90. const auto id = messages->emplaceShortcut(name);
  91. showOther(ShortcutMessagesId(id));
  92. close();
  93. };
  94. controller->show(Box(
  95. EditShortcutNameBox,
  96. QString(),
  97. crl::guard(this, submit)));
  98. });
  99. if (count > 0) {
  100. AddSkip(addWrap);
  101. AddDivider(addWrap);
  102. AddSkip(addWrap);
  103. }
  104. }
  105. if (const auto width = content->width()) {
  106. content->resizeToWidth(width);
  107. }
  108. }, lifetime());
  109. const auto inner = content->add(
  110. object_ptr<Ui::VerticalLayout>(content));
  111. rpl::single(rpl::empty) | rpl::then(
  112. messages->shortcutsChanged()
  113. ) | rpl::start_with_next([=] {
  114. auto old = inner->count();
  115. const auto &shortcuts = messages->shortcuts();
  116. for (const auto &[_, shortcut]
  117. : shortcuts.list | ranges::views::reverse) {
  118. if (!shortcut.count) {
  119. continue;
  120. }
  121. const auto name = shortcut.name;
  122. AddButtonWithLabel(
  123. inner,
  124. rpl::single('/' + name),
  125. tr::lng_forum_messages(
  126. lt_count,
  127. rpl::single(1. * shortcut.count)),
  128. st::settingsButtonNoIcon
  129. )->setClickedCallback([=] {
  130. const auto id = messages->emplaceShortcut(name);
  131. showOther(ShortcutMessagesId(id));
  132. });
  133. if (old) {
  134. delete inner->widgetAt(0);
  135. --old;
  136. }
  137. }
  138. while (old--) {
  139. delete inner->widgetAt(0);
  140. }
  141. _count = inner->count();
  142. }, content->lifetime());
  143. Ui::ResizeFitChild(this, content);
  144. }
  145. [[nodiscard]] bool ValidShortcutName(const QString &name) {
  146. if (name.isEmpty() || name.size() > kShortcutLimit) {
  147. return false;
  148. }
  149. for (const auto &ch : name) {
  150. if (!ch.isLetterOrNumber()
  151. && (ch != QChar('_'))
  152. && (ch.unicode() != 0x200c)
  153. && (ch.unicode() != 0x00b7)
  154. && (ch.unicode() < 0x0d80 || ch.unicode() > 0x0dff)) {
  155. return false;
  156. }
  157. }
  158. return true;
  159. }
  160. } // namespace
  161. Type QuickRepliesId() {
  162. return QuickReplies::Id();
  163. }
  164. void EditShortcutNameBox(
  165. not_null<Ui::GenericBox*> box,
  166. QString name,
  167. Fn<void(QString, Fn<void()>)> submit) {
  168. name = name.trimmed();
  169. const auto editing = !name.isEmpty();
  170. box->setTitle(editing
  171. ? tr::lng_replies_edit_title()
  172. : tr::lng_replies_add_title());
  173. box->addRow(object_ptr<Ui::FlatLabel>(
  174. box,
  175. (editing
  176. ? tr::lng_replies_edit_about()
  177. : tr::lng_replies_add_shortcut()),
  178. st::settingsAddReplyLabel));
  179. const auto field = box->addRow(object_ptr<Ui::InputField>(
  180. box,
  181. st::settingsAddReplyField,
  182. tr::lng_replies_add_placeholder(),
  183. name));
  184. box->setFocusCallback([=] {
  185. field->setFocusFast();
  186. });
  187. field->selectAll();
  188. field->setMaxLength(kShortcutLimit * 2);
  189. Ui::AddLengthLimitLabel(field, kShortcutLimit);
  190. const auto callback = [=] {
  191. const auto name = field->getLastText().trimmed();
  192. if (!ValidShortcutName(name)) {
  193. field->showError();
  194. } else {
  195. submit(name, [weak = Ui::MakeWeak(box)] {
  196. if (const auto strong = weak.data()) {
  197. strong->closeBox();
  198. }
  199. });
  200. }
  201. };
  202. field->submits(
  203. ) | rpl::start_with_next(callback, field->lifetime());
  204. box->addButton(tr::lng_settings_save(), callback);
  205. box->addButton(tr::lng_cancel(), [=] {
  206. box->closeBox();
  207. });
  208. }
  209. } // namespace Settings