settings_shortcut_messages.cpp 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618
  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_shortcut_messages.h"
  8. #include "api/api_editing.h"
  9. #include "api/api_sending.h"
  10. #include "apiwrap.h"
  11. #include "base/call_delayed.h"
  12. #include "boxes/delete_messages_box.h"
  13. #include "boxes/premium_limits_box.h"
  14. #include "boxes/premium_preview_box.h"
  15. #include "boxes/send_files_box.h"
  16. #include "chat_helpers/tabbed_selector.h"
  17. #include "core/file_utilities.h"
  18. #include "core/mime_type.h"
  19. #include "data/business/data_shortcut_messages.h"
  20. #include "data/data_message_reaction_id.h"
  21. #include "data/data_premium_limits.h"
  22. #include "data/data_session.h"
  23. #include "data/data_user.h"
  24. #include "history/view/controls/compose_controls_common.h"
  25. #include "history/view/controls/history_view_compose_controls.h"
  26. #include "history/view/history_view_corner_buttons.h"
  27. #include "history/view/history_view_empty_list_bubble.h"
  28. #include "history/view/history_view_list_widget.h"
  29. #include "history/view/history_view_service_message.h"
  30. #include "history/view/history_view_sticker_toast.h"
  31. #include "history/history.h"
  32. #include "history/history_item.h"
  33. #include "info/info_wrap_widget.h"
  34. #include "inline_bots/inline_bot_result.h"
  35. #include "lang/lang_keys.h"
  36. #include "lang/lang_numbers_animation.h"
  37. #include "main/main_account.h"
  38. #include "main/main_app_config.h"
  39. #include "main/main_session.h"
  40. #include "menu/menu_send.h"
  41. #include "settings/business/settings_quick_replies.h"
  42. #include "settings/business/settings_recipients_helper.h"
  43. #include "storage/localimageloader.h"
  44. #include "storage/storage_account.h"
  45. #include "storage/storage_media_prepare.h"
  46. #include "storage/storage_shared_media.h"
  47. #include "ui/boxes/confirm_box.h"
  48. #include "ui/chat/attach/attach_send_files_way.h"
  49. #include "ui/chat/chat_style.h"
  50. #include "ui/chat/chat_theme.h"
  51. #include "ui/controls/jump_down_button.h"
  52. #include "ui/text/format_values.h"
  53. #include "ui/text/text_utilities.h"
  54. #include "ui/widgets/menu/menu_add_action_callback.h"
  55. #include "ui/widgets/scroll_area.h"
  56. #include "ui/painter.h"
  57. #include "window/themes/window_theme.h"
  58. #include "window/section_widget.h"
  59. #include "window/window_session_controller.h"
  60. #include "styles/style_boxes.h"
  61. #include "styles/style_chat_helpers.h"
  62. #include "styles/style_chat.h"
  63. #include "styles/style_menu_icons.h"
  64. #include "styles/style_layers.h"
  65. namespace Settings {
  66. namespace {
  67. using namespace HistoryView;
  68. class ShortcutMessages
  69. : public AbstractSection
  70. , private WindowListDelegate
  71. , private CornerButtonsDelegate {
  72. public:
  73. ShortcutMessages(
  74. QWidget *parent,
  75. not_null<Window::SessionController*> controller,
  76. not_null<Ui::ScrollArea*> scroll,
  77. rpl::producer<Container> containerValue,
  78. BusinessShortcutId shortcutId);
  79. ~ShortcutMessages();
  80. [[nodiscard]] static Type Id(BusinessShortcutId shortcutId);
  81. [[nodiscard]] Type id() const final override {
  82. return Id(_shortcutId.current());
  83. }
  84. [[nodiscard]] rpl::producer<QString> title() override;
  85. [[nodiscard]] rpl::producer<> sectionShowBack() override;
  86. void setInnerFocus() override;
  87. rpl::producer<Info::SelectedItems> selectedListValue() override;
  88. void selectionAction(Info::SelectionAction action) override;
  89. void fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) override;
  90. bool paintOuter(
  91. not_null<QWidget*> outer,
  92. int maxVisibleHeight,
  93. QRect clip) override;
  94. private:
  95. void outerResized();
  96. void updateComposeControlsPosition();
  97. // ListDelegate interface.
  98. Context listContext() override;
  99. bool listScrollTo(int top, bool syntetic = true) override;
  100. void listCancelRequest() override;
  101. void listDeleteRequest() override;
  102. void listTryProcessKeyInput(not_null<QKeyEvent*> e) override;
  103. rpl::producer<Data::MessagesSlice> listSource(
  104. Data::MessagePosition aroundId,
  105. int limitBefore,
  106. int limitAfter) override;
  107. bool listAllowsMultiSelect() override;
  108. bool listIsItemGoodForSelection(not_null<HistoryItem*> item) override;
  109. bool listIsLessInOrder(
  110. not_null<HistoryItem*> first,
  111. not_null<HistoryItem*> second) override;
  112. void listSelectionChanged(SelectedItems &&items) override;
  113. void listMarkReadTill(not_null<HistoryItem*> item) override;
  114. void listMarkContentsRead(
  115. const base::flat_set<not_null<HistoryItem*>> &items) override;
  116. MessagesBarData listMessagesBar(
  117. const std::vector<not_null<Element*>> &elements) override;
  118. void listContentRefreshed() override;
  119. void listUpdateDateLink(
  120. ClickHandlerPtr &link,
  121. not_null<Element*> view) override;
  122. bool listElementHideReply(not_null<const Element*> view) override;
  123. bool listElementShownUnread(not_null<const Element*> view) override;
  124. bool listIsGoodForAroundPosition(
  125. not_null<const Element *> view) override;
  126. void listSendBotCommand(
  127. const QString &command,
  128. const FullMsgId &context) override;
  129. void listSearch(
  130. const QString &query,
  131. const FullMsgId &context) override;
  132. void listHandleViaClick(not_null<UserData*> bot) override;
  133. not_null<Ui::ChatTheme*> listChatTheme() override;
  134. CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override;
  135. CopyRestrictionType listCopyMediaRestrictionType(
  136. not_null<HistoryItem*> item) override;
  137. CopyRestrictionType listSelectRestrictionType() override;
  138. auto listAllowedReactionsValue()
  139. -> rpl::producer<Data::AllowedReactions> override;
  140. void listShowPremiumToast(not_null<DocumentData*> document) override;
  141. void listOpenPhoto(
  142. not_null<PhotoData*> photo,
  143. FullMsgId context) override;
  144. void listOpenDocument(
  145. not_null<DocumentData*> document,
  146. FullMsgId context,
  147. bool showInMediaView) override;
  148. void listPaintEmpty(
  149. Painter &p,
  150. const Ui::ChatPaintContext &context) override;
  151. QString listElementAuthorRank(not_null<const Element*> view) override;
  152. bool listElementHideTopicButton(not_null<const Element*> view) override;
  153. History *listTranslateHistory() override;
  154. void listAddTranslatedItems(
  155. not_null<TranslateTracker*> tracker) override;
  156. bool listIgnorePaintEvent(QWidget *w, QPaintEvent *e) override;
  157. // CornerButtonsDelegate delegate.
  158. void cornerButtonsShowAtPosition(
  159. Data::MessagePosition position) override;
  160. Data::Thread *cornerButtonsThread() override;
  161. FullMsgId cornerButtonsCurrentId() override;
  162. bool cornerButtonsIgnoreVisibility() override;
  163. std::optional<bool> cornerButtonsDownShown() override;
  164. bool cornerButtonsUnreadMayBeShown() override;
  165. bool cornerButtonsHas(CornerButtonType type) override;
  166. QPointer<Ui::RpWidget> createPinnedToBottom(
  167. not_null<Ui::RpWidget*> parent) override;
  168. void setupComposeControls();
  169. void processScroll();
  170. void updateInnerVisibleArea();
  171. void checkReplyReturns();
  172. void confirmDeleteSelected();
  173. void clearSelected();
  174. void uploadFile(const QByteArray &fileContent, SendMediaType type);
  175. bool confirmSendingFiles(
  176. QImage &&image,
  177. QByteArray &&content,
  178. std::optional<bool> overrideSendImagesAsPhotos = std::nullopt,
  179. const QString &insertTextOnCancel = QString());
  180. bool confirmSendingFiles(
  181. const QStringList &files,
  182. const QString &insertTextOnCancel);
  183. bool confirmSendingFiles(
  184. Ui::PreparedList &&list,
  185. const QString &insertTextOnCancel = QString());
  186. bool confirmSendingFiles(
  187. not_null<const QMimeData*> data,
  188. std::optional<bool> overrideSendImagesAsPhotos,
  189. const QString &insertTextOnCancel = QString());
  190. bool showSendingFilesError(const Ui::PreparedList &list) const;
  191. bool showSendingFilesError(
  192. const Ui::PreparedList &list,
  193. std::optional<bool> compress) const;
  194. void sendingFilesConfirmed(
  195. Ui::PreparedList &&list,
  196. Ui::SendFilesWay way,
  197. TextWithTags &&caption,
  198. Api::SendOptions options,
  199. bool ctrlShiftEnter);
  200. bool sendExistingDocument(
  201. not_null<DocumentData*> document,
  202. Api::SendOptions options,
  203. std::optional<MsgId> localId);
  204. void sendExistingPhoto(not_null<PhotoData*> photo);
  205. bool sendExistingPhoto(
  206. not_null<PhotoData*> photo,
  207. Api::SendOptions options);
  208. void sendInlineResult(
  209. std::shared_ptr<InlineBots::Result> result,
  210. not_null<UserData*> bot);
  211. void sendInlineResult(
  212. std::shared_ptr<InlineBots::Result> result,
  213. not_null<UserData*> bot,
  214. Api::SendOptions options,
  215. std::optional<MsgId> localMessageId);
  216. [[nodiscard]] Api::SendAction prepareSendAction(
  217. Api::SendOptions options) const;
  218. void send();
  219. void send(Api::SendOptions options);
  220. void sendVoice(Controls::VoiceToSend &&data);
  221. void edit(
  222. not_null<HistoryItem*> item,
  223. Api::SendOptions options,
  224. mtpRequestId *const saveEditMsgRequestId,
  225. bool spoilered);
  226. void chooseAttach(std::optional<bool> overrideSendImagesAsPhotos);
  227. [[nodiscard]] FullReplyTo replyTo() const;
  228. void doSetInnerFocus();
  229. void showAtPosition(
  230. Data::MessagePosition position,
  231. FullMsgId originItemId = {});
  232. void showAtPosition(
  233. Data::MessagePosition position,
  234. FullMsgId originItemId,
  235. const Window::SectionShow &params);
  236. void showAtEnd();
  237. void finishSending();
  238. void refreshEmptyText();
  239. bool showPremiumRequired() const;
  240. const not_null<Window::SessionController*> _controller;
  241. const not_null<Main::Session*> _session;
  242. const not_null<Ui::ScrollArea*> _scroll;
  243. const not_null<History*> _history;
  244. rpl::variable<BusinessShortcutId> _shortcutId;
  245. rpl::variable<QString> _shortcut;
  246. rpl::variable<Container> _container;
  247. rpl::variable<int> _count;
  248. std::shared_ptr<Ui::ChatStyle> _style;
  249. std::shared_ptr<Ui::ChatTheme> _theme;
  250. QPointer<ListWidget> _inner;
  251. std::unique_ptr<Ui::RpWidget> _controlsWrap;
  252. std::unique_ptr<ComposeControls> _composeControls;
  253. rpl::event_stream<> _showBackRequests;
  254. bool _skipScrollEvent = false;
  255. QSize _inOuterResize;
  256. QSize _pendingOuterResize;
  257. const style::icon *_emptyIcon = nullptr;
  258. Ui::Text::String _emptyText;
  259. int _emptyTextWidth = 0;
  260. int _emptyTextHeight = 0;
  261. rpl::variable<Info::SelectedItems> _selectedItems
  262. = Info::SelectedItems(Storage::SharedMediaType::kCount);
  263. std::unique_ptr<StickerToast> _stickerToast;
  264. FullMsgId _lastShownAt;
  265. CornerButtons _cornerButtons;
  266. Data::MessagesSlice _lastSlice;
  267. bool _choosingAttach = false;
  268. };
  269. struct Factory final : AbstractSectionFactory {
  270. explicit Factory(BusinessShortcutId shortcutId)
  271. : shortcutId(shortcutId) {
  272. }
  273. object_ptr<AbstractSection> create(
  274. not_null<QWidget*> parent,
  275. not_null<Window::SessionController*> controller,
  276. not_null<Ui::ScrollArea*> scroll,
  277. rpl::producer<Container> containerValue
  278. ) const final override {
  279. return object_ptr<ShortcutMessages>(
  280. parent,
  281. controller,
  282. scroll,
  283. std::move(containerValue),
  284. shortcutId);
  285. }
  286. const BusinessShortcutId shortcutId = {};
  287. };
  288. [[nodiscard]] bool IsAway(const QString &shortcut) {
  289. return (shortcut == u"away"_q);
  290. }
  291. [[nodiscard]] bool IsGreeting(const QString &shortcut) {
  292. return (shortcut == u"hello"_q);
  293. }
  294. ShortcutMessages::ShortcutMessages(
  295. QWidget *parent,
  296. not_null<Window::SessionController*> controller,
  297. not_null<Ui::ScrollArea*> scroll,
  298. rpl::producer<Container> containerValue,
  299. BusinessShortcutId shortcutId)
  300. : AbstractSection(parent)
  301. , WindowListDelegate(controller)
  302. , _controller(controller)
  303. , _session(&controller->session())
  304. , _scroll(scroll)
  305. , _history(_session->data().history(_session->user()->id))
  306. , _shortcutId(shortcutId)
  307. , _shortcut(
  308. _session->data().shortcutMessages().lookupShortcut(shortcutId).name)
  309. , _container(std::move(containerValue))
  310. , _cornerButtons(
  311. _scroll,
  312. controller->chatStyle(),
  313. static_cast<HistoryView::CornerButtonsDelegate*>(this)) {
  314. const auto messages = &_session->data().shortcutMessages();
  315. messages->shortcutIdChanged(
  316. ) | rpl::start_with_next([=](Data::ShortcutIdChange change) {
  317. if (change.oldId == _shortcutId.current()) {
  318. if (change.newId) {
  319. _shortcutId = change.newId;
  320. } else {
  321. _showBackRequests.fire({});
  322. }
  323. }
  324. }, lifetime());
  325. messages->shortcutsChanged(
  326. ) | rpl::start_with_next([=] {
  327. _shortcut = messages->lookupShortcut(_shortcutId.current()).name;
  328. }, lifetime());
  329. controller->chatStyle()->paletteChanged(
  330. ) | rpl::start_with_next([=] {
  331. _scroll->updateBars();
  332. }, _scroll->lifetime());
  333. _style = std::make_shared<Ui::ChatStyle>(_session->colorIndicesValue());
  334. _theme = std::shared_ptr<Ui::ChatTheme>(
  335. Window::Theme::DefaultChatThemeOn(lifetime()));
  336. _inner = Ui::CreateChild<ListWidget>(
  337. this,
  338. &controller->session(),
  339. static_cast<ListDelegate*>(this));
  340. _inner->overrideIsChatWide(false);
  341. _scroll->sizeValue() | rpl::filter([](QSize size) {
  342. return !size.isEmpty();
  343. }) | rpl::start_with_next([=] {
  344. outerResized();
  345. }, lifetime());
  346. _scroll->scrolls(
  347. ) | rpl::start_with_next([=] {
  348. processScroll();
  349. }, lifetime());
  350. _shortcut.value() | rpl::start_with_next([=] {
  351. refreshEmptyText();
  352. _inner->update();
  353. }, lifetime());
  354. _inner->editMessageRequested(
  355. ) | rpl::start_with_next([=](auto fullId) {
  356. if (const auto item = _session->data().message(fullId)) {
  357. const auto media = item->media();
  358. if (!media || media->webpage() || media->allowsEditCaption()) {
  359. _composeControls->editMessage(
  360. fullId,
  361. _inner->getSelectedTextRange(item));
  362. }
  363. }
  364. }, _inner->lifetime());
  365. _inner->heightValue() | rpl::start_with_next([=](int height) {
  366. resize(width(), height);
  367. }, lifetime());
  368. }
  369. ShortcutMessages::~ShortcutMessages() = default;
  370. void ShortcutMessages::refreshEmptyText() {
  371. const auto &shortcut = _shortcut.current();
  372. const auto away = IsAway(shortcut);
  373. const auto greeting = !away && IsGreeting(shortcut);
  374. auto text = away
  375. ? tr::lng_away_empty_title(
  376. tr::now,
  377. Ui::Text::Bold
  378. ).append("\n\n").append(tr::lng_away_empty_about(tr::now))
  379. : greeting
  380. ? tr::lng_greeting_empty_title(
  381. tr::now,
  382. Ui::Text::Bold
  383. ).append("\n\n").append(tr::lng_greeting_empty_about(tr::now))
  384. : tr::lng_replies_empty_title(
  385. tr::now,
  386. Ui::Text::Bold
  387. ).append("\n\n").append(tr::lng_replies_empty_about(
  388. tr::now,
  389. lt_shortcut,
  390. Ui::Text::Bold('/' + shortcut),
  391. Ui::Text::WithEntities));
  392. _emptyIcon = away
  393. ? &st::awayEmptyIcon
  394. : greeting
  395. ? &st::greetingEmptyIcon
  396. : &st::repliesEmptyIcon;
  397. const auto padding = st::repliesEmptyPadding;
  398. const auto minWidth = st::repliesEmptyWidth / 4;
  399. const auto maxWidth = std::max(
  400. minWidth + 1,
  401. st::repliesEmptyWidth - padding.left() - padding.right());
  402. _emptyText = Ui::Text::String(
  403. st::messageTextStyle,
  404. text,
  405. kMarkupTextOptions,
  406. minWidth);
  407. const auto countHeight = [&](int width) {
  408. return _emptyText.countHeight(width);
  409. };
  410. _emptyTextWidth = Ui::FindNiceTooltipWidth(
  411. minWidth,
  412. maxWidth,
  413. countHeight);
  414. _emptyTextHeight = countHeight(_emptyTextWidth);
  415. }
  416. Type ShortcutMessages::Id(BusinessShortcutId shortcutId) {
  417. return std::make_shared<Factory>(shortcutId);
  418. }
  419. rpl::producer<QString> ShortcutMessages::title() {
  420. return _shortcut.value() | rpl::map([=](const QString &shortcut) {
  421. return IsAway(shortcut)
  422. ? tr::lng_away_title()
  423. : IsGreeting(shortcut)
  424. ? tr::lng_greeting_title()
  425. : rpl::single('/' + shortcut);
  426. }) | rpl::flatten_latest();
  427. }
  428. void ShortcutMessages::processScroll() {
  429. if (_skipScrollEvent) {
  430. return;
  431. }
  432. updateInnerVisibleArea();
  433. }
  434. void ShortcutMessages::updateInnerVisibleArea() {
  435. if (!_inner->animatedScrolling()) {
  436. checkReplyReturns();
  437. }
  438. const auto scrollTop = _scroll->scrollTop();
  439. _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
  440. _cornerButtons.updateJumpDownVisibility();
  441. _cornerButtons.updateUnreadThingsVisibility();
  442. }
  443. rpl::producer<> ShortcutMessages::sectionShowBack() {
  444. return _showBackRequests.events();
  445. }
  446. void ShortcutMessages::setInnerFocus() {
  447. _composeControls->focus();
  448. }
  449. rpl::producer<Info::SelectedItems> ShortcutMessages::selectedListValue() {
  450. return _selectedItems.value();
  451. }
  452. void ShortcutMessages::selectionAction(Info::SelectionAction action) {
  453. switch (action) {
  454. case Info::SelectionAction::Clear: clearSelected(); return;
  455. case Info::SelectionAction::Delete: confirmDeleteSelected(); return;
  456. }
  457. Unexpected("Action in ShortcutMessages::selectionAction.");
  458. }
  459. void ShortcutMessages::fillTopBarMenu(
  460. const Ui::Menu::MenuCallback &addAction) {
  461. const auto owner = &_controller->session().data();
  462. const auto messages = &owner->shortcutMessages();
  463. addAction(tr::lng_context_edit_shortcut(tr::now), [=] {
  464. if (!_controller->session().premium()) {
  465. ShowPremiumPreviewToBuy(
  466. _controller,
  467. PremiumFeature::QuickReplies);
  468. return;
  469. }
  470. const auto submit = [=](QString name, Fn<void()> close) {
  471. const auto id = _shortcutId.current();
  472. const auto error = [=](QString text) {
  473. if (!text.isEmpty()) {
  474. _controller->showToast((text == u"SHORTCUT_OCCUPIED"_q)
  475. ? tr::lng_replies_error_occupied(tr::now)
  476. : text);
  477. }
  478. };
  479. messages->editShortcut(id, name, close, crl::guard(this, error));
  480. };
  481. const auto name = _shortcut.current();
  482. _controller->show(
  483. Box(EditShortcutNameBox, name, crl::guard(this, submit)));
  484. }, &st::menuIconEdit);
  485. const auto justDelete = crl::guard(this, [=] {
  486. messages->removeShortcut(_shortcutId.current());
  487. });
  488. const auto confirmDeleteShortcut = [=] {
  489. const auto slice = messages->list(_shortcutId.current());
  490. if (slice.fullCount == 0) {
  491. justDelete();
  492. } else {
  493. const auto confirmed = [=](Fn<void()> close) {
  494. justDelete();
  495. close();
  496. };
  497. _controller->show(Ui::MakeConfirmBox({
  498. .text = { tr::lng_replies_delete_sure() },
  499. .confirmed = confirmed,
  500. .confirmText = tr::lng_box_delete(),
  501. .confirmStyle = &st::attentionBoxButton,
  502. }));
  503. }
  504. };
  505. addAction({
  506. .text = tr::lng_context_delete_shortcut(tr::now),
  507. .handler = crl::guard(this, confirmDeleteShortcut),
  508. .icon = &st::menuIconDeleteAttention,
  509. .isAttention = true,
  510. });
  511. }
  512. bool ShortcutMessages::paintOuter(
  513. not_null<QWidget*> outer,
  514. int maxVisibleHeight,
  515. QRect clip) {
  516. Window::SectionWidget::PaintBackground(
  517. _theme.get(),
  518. outer,
  519. std::max(outer->height(), maxVisibleHeight),
  520. 0,
  521. clip);
  522. return true;
  523. }
  524. void ShortcutMessages::outerResized() {
  525. const auto outer = _scroll->size();
  526. if (!_inOuterResize.isEmpty()) {
  527. _pendingOuterResize = (_inOuterResize != outer)
  528. ? outer
  529. : QSize();
  530. return;
  531. }
  532. _inOuterResize = outer;
  533. do {
  534. const auto newScrollTop = _scroll->isHidden()
  535. ? std::nullopt
  536. : _scroll->scrollTop()
  537. ? base::make_optional(_scroll->scrollTop())
  538. : 0;
  539. _skipScrollEvent = true;
  540. const auto minHeight = (_container.current() == Container::Layer)
  541. ? st::boxWidth
  542. : _inOuterResize.height();
  543. _inner->resizeToWidth(_inOuterResize.width(), minHeight);
  544. _skipScrollEvent = false;
  545. if (!_scroll->isHidden() && newScrollTop) {
  546. _scroll->scrollToY(*newScrollTop);
  547. }
  548. _inOuterResize = base::take(_pendingOuterResize);
  549. } while (!_inOuterResize.isEmpty());
  550. if (!_scroll->isHidden()) {
  551. updateInnerVisibleArea();
  552. }
  553. updateComposeControlsPosition();
  554. _cornerButtons.updatePositions();
  555. }
  556. void ShortcutMessages::updateComposeControlsPosition() {
  557. const auto bottom = _scroll->parentWidget()->height();
  558. const auto controlsHeight = _composeControls->heightCurrent();
  559. _composeControls->move(0, bottom - controlsHeight + st::boxRadius);
  560. _composeControls->setAutocompleteBoundingRect(_scroll->geometry());
  561. }
  562. void ShortcutMessages::setupComposeControls() {
  563. _shortcutId.value() | rpl::start_with_next([=](BusinessShortcutId id) {
  564. _composeControls->updateShortcutId(id);
  565. }, lifetime());
  566. const auto state = Dialogs::EntryState{
  567. .key = Dialogs::Key{ _history },
  568. .section = Dialogs::EntryState::Section::ShortcutMessages,
  569. .currentReplyTo = replyTo(),
  570. };
  571. _composeControls->setCurrentDialogsEntryState(state);
  572. auto writeRestriction = rpl::combine(
  573. _count.value(),
  574. ShortcutMessagesLimitValue(_session)
  575. ) | rpl::map([=](int count, int limit) {
  576. return (count >= limit)
  577. ? Controls::WriteRestriction{
  578. .text = tr::lng_business_limit_reached(
  579. tr::now,
  580. lt_count,
  581. limit),
  582. .type = Controls::WriteRestrictionType::Rights,
  583. } : Controls::WriteRestriction();
  584. });
  585. _composeControls->setHistory({
  586. .history = _history.get(),
  587. .writeRestriction = std::move(writeRestriction),
  588. });
  589. _composeControls->cancelRequests(
  590. ) | rpl::start_with_next([=] {
  591. listCancelRequest();
  592. }, lifetime());
  593. _composeControls->sendRequests(
  594. ) | rpl::start_with_next([=] {
  595. send();
  596. }, lifetime());
  597. _composeControls->sendVoiceRequests(
  598. ) | rpl::start_with_next([=](ComposeControls::VoiceToSend &&data) {
  599. sendVoice(std::move(data));
  600. }, lifetime());
  601. _composeControls->sendCommandRequests(
  602. ) | rpl::start_with_next([=](const QString &command) {
  603. listSendBotCommand(command, FullMsgId());
  604. }, lifetime());
  605. const auto saveEditMsgRequestId = lifetime().make_state<mtpRequestId>(0);
  606. _composeControls->editRequests(
  607. ) | rpl::start_with_next([=](auto data) {
  608. if (const auto item = _session->data().message(data.fullId)) {
  609. if (item->isBusinessShortcut()) {
  610. const auto spoiler = data.spoilered;
  611. edit(item, data.options, saveEditMsgRequestId, spoiler);
  612. }
  613. }
  614. }, lifetime());
  615. _composeControls->attachRequests(
  616. ) | rpl::filter([=] {
  617. return !_choosingAttach;
  618. }) | rpl::start_with_next([=](std::optional<bool> overrideCompress) {
  619. _choosingAttach = true;
  620. base::call_delayed(st::historyAttach.ripple.hideDuration, this, [=] {
  621. _choosingAttach = false;
  622. chooseAttach(overrideCompress);
  623. });
  624. }, lifetime());
  625. _composeControls->fileChosen(
  626. ) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
  627. _controller->hideLayer(anim::type::normal);
  628. sendExistingDocument(data.document, {}, std::nullopt);
  629. }, lifetime());
  630. _composeControls->photoChosen(
  631. ) | rpl::start_with_next([=](ChatHelpers::PhotoChosen chosen) {
  632. sendExistingPhoto(chosen.photo);
  633. }, lifetime());
  634. _composeControls->inlineResultChosen(
  635. ) | rpl::start_with_next([=](ChatHelpers::InlineChosen chosen) {
  636. sendInlineResult(chosen.result, chosen.bot);
  637. }, lifetime());
  638. _composeControls->jumpToItemRequests(
  639. ) | rpl::start_with_next([=](FullReplyTo to) {
  640. if (const auto item = _session->data().message(to.messageId)) {
  641. showAtPosition(item->position());
  642. }
  643. }, lifetime());
  644. rpl::merge(
  645. _composeControls->scrollKeyEvents(),
  646. _inner->scrollKeyEvents()
  647. ) | rpl::start_with_next([=](not_null<QKeyEvent*> e) {
  648. _scroll->keyPressEvent(e);
  649. }, lifetime());
  650. _composeControls->editLastMessageRequests(
  651. ) | rpl::start_with_next([=](not_null<QKeyEvent*> e) {
  652. if (!_inner->lastMessageEditRequestNotify()) {
  653. _scroll->keyPressEvent(e);
  654. }
  655. }, lifetime());
  656. _composeControls->setMimeDataHook([=](
  657. not_null<const QMimeData*> data,
  658. Ui::InputField::MimeAction action) {
  659. if (action == Ui::InputField::MimeAction::Check) {
  660. return Core::CanSendFiles(data);
  661. } else if (action == Ui::InputField::MimeAction::Insert) {
  662. return confirmSendingFiles(
  663. data,
  664. std::nullopt,
  665. Core::ReadMimeText(data));
  666. }
  667. Unexpected("action in MimeData hook.");
  668. });
  669. _composeControls->lockShowStarts(
  670. ) | rpl::start_with_next([=] {
  671. _cornerButtons.updateJumpDownVisibility();
  672. _cornerButtons.updateUnreadThingsVisibility();
  673. }, lifetime());
  674. _composeControls->viewportEvents(
  675. ) | rpl::start_with_next([=](not_null<QEvent*> e) {
  676. _scroll->viewportEvent(e);
  677. }, lifetime());
  678. _controlsWrap->widthValue() | rpl::start_with_next([=](int width) {
  679. _composeControls->resizeToWidth(width);
  680. }, _controlsWrap->lifetime());
  681. _composeControls->height(
  682. ) | rpl::start_with_next([=](int height) {
  683. const auto wasMax = (_scroll->scrollTopMax() == _scroll->scrollTop());
  684. _controlsWrap->resize(width(), height - st::boxRadius);
  685. updateComposeControlsPosition();
  686. if (wasMax) {
  687. listScrollTo(_scroll->scrollTopMax());
  688. }
  689. }, lifetime());
  690. }
  691. QPointer<Ui::RpWidget> ShortcutMessages::createPinnedToBottom(
  692. not_null<Ui::RpWidget*> parent) {
  693. auto placeholder = rpl::deferred([=] {
  694. return _shortcutId.value();
  695. }) | rpl::map([=](BusinessShortcutId id) {
  696. return _session->data().shortcutMessages().lookupShortcut(id).name;
  697. }) | rpl::map([=](const QString &shortcut) {
  698. return (shortcut == u"away"_q)
  699. ? tr::lng_away_message_placeholder()
  700. : (shortcut == u"hello"_q)
  701. ? tr::lng_greeting_message_placeholder()
  702. : tr::lng_replies_message_placeholder();
  703. }) | rpl::flatten_latest();
  704. _controlsWrap = std::make_unique<Ui::RpWidget>(parent);
  705. _composeControls = std::make_unique<ComposeControls>(
  706. dynamic_cast<Ui::RpWidget*>(_scroll->parentWidget()),
  707. ComposeControlsDescriptor{
  708. .stOverride = &st::repliesComposeControls,
  709. .show = _controller->uiShow(),
  710. .unavailableEmojiPasted = [=](not_null<DocumentData*> emoji) {
  711. listShowPremiumToast(emoji);
  712. },
  713. .mode = HistoryView::ComposeControlsMode::Normal,
  714. .sendMenuDetails = [] { return SendMenu::Details(); },
  715. .regularWindow = _controller,
  716. .stickerOrEmojiChosen = _controller->stickerOrEmojiChosen(),
  717. .customPlaceholder = std::move(placeholder),
  718. .panelsLevel = Window::GifPauseReason::Layer,
  719. .voiceCustomCancelText = tr::lng_record_cancel_stories(tr::now),
  720. .voiceLockFromBottom = true,
  721. .features = {
  722. .sendAs = false,
  723. .ttlInfo = false,
  724. .botCommandSend = false,
  725. .silentBroadcastToggle = false,
  726. .attachBotsMenu = false,
  727. .megagroupSet = false,
  728. .commonTabbedPanel = false,
  729. },
  730. });
  731. setupComposeControls();
  732. showAtEnd();
  733. return _controlsWrap.get();
  734. }
  735. Context ShortcutMessages::listContext() {
  736. return Context::ShortcutMessages;
  737. }
  738. bool ShortcutMessages::listScrollTo(int top, bool syntetic) {
  739. top = std::clamp(top, 0, _scroll->scrollTopMax());
  740. if (_scroll->scrollTop() == top) {
  741. updateInnerVisibleArea();
  742. return false;
  743. }
  744. _scroll->scrollToY(top);
  745. return true;
  746. }
  747. void ShortcutMessages::listCancelRequest() {
  748. if (_inner && !_inner->getSelectedItems().empty()) {
  749. clearSelected();
  750. return;
  751. } else if (_composeControls->handleCancelRequest()) {
  752. return;
  753. }
  754. _showBackRequests.fire({});
  755. }
  756. void ShortcutMessages::listDeleteRequest() {
  757. confirmDeleteSelected();
  758. }
  759. void ShortcutMessages::listTryProcessKeyInput(not_null<QKeyEvent*> e) {
  760. _composeControls->tryProcessKeyInput(e);
  761. }
  762. rpl::producer<Data::MessagesSlice> ShortcutMessages::listSource(
  763. Data::MessagePosition aroundId,
  764. int limitBefore,
  765. int limitAfter) {
  766. const auto messages = &_session->data().shortcutMessages();
  767. return _shortcutId.value(
  768. ) | rpl::map([=](BusinessShortcutId shortcutId) {
  769. return rpl::single(rpl::empty) | rpl::then(
  770. messages->updates(shortcutId)
  771. ) | rpl::map([=] {
  772. return messages->list(shortcutId);
  773. });
  774. }) | rpl::flatten_latest(
  775. ) | rpl::after_next([=](const Data::MessagesSlice &slice) {
  776. _count = slice.fullCount.value_or(
  777. messages->count(_shortcutId.current()));
  778. });
  779. }
  780. bool ShortcutMessages::listAllowsMultiSelect() {
  781. return true;
  782. }
  783. bool ShortcutMessages::listIsItemGoodForSelection(
  784. not_null<HistoryItem*> item) {
  785. return !item->isSending() && !item->hasFailed();
  786. }
  787. bool ShortcutMessages::listIsLessInOrder(
  788. not_null<HistoryItem*> first,
  789. not_null<HistoryItem*> second) {
  790. return first->position() < second->position();
  791. }
  792. void ShortcutMessages::listSelectionChanged(SelectedItems &&items) {
  793. auto value = Info::SelectedItems();
  794. value.title = [](int count) {
  795. return tr::lng_forum_messages(
  796. tr::now,
  797. lt_count,
  798. count,
  799. Ui::StringWithNumbers::FromString);
  800. };
  801. value.list = items | ranges::views::transform([](SelectedItem item) {
  802. auto result = Info::SelectedItem(GlobalMsgId{ item.msgId });
  803. result.canDelete = item.canDelete;
  804. return result;
  805. }) | ranges::to_vector;
  806. _selectedItems = std::move(value);
  807. if (items.empty()) {
  808. doSetInnerFocus();
  809. }
  810. }
  811. void ShortcutMessages::listMarkReadTill(not_null<HistoryItem*> item) {
  812. }
  813. void ShortcutMessages::listMarkContentsRead(
  814. const base::flat_set<not_null<HistoryItem*>> &items) {
  815. }
  816. MessagesBarData ShortcutMessages::listMessagesBar(
  817. const std::vector<not_null<Element*>> &elements) {
  818. return {};
  819. }
  820. void ShortcutMessages::listContentRefreshed() {
  821. }
  822. void ShortcutMessages::listUpdateDateLink(
  823. ClickHandlerPtr &link,
  824. not_null<Element*> view) {
  825. }
  826. bool ShortcutMessages::listElementHideReply(not_null<const Element*> view) {
  827. return false;
  828. }
  829. bool ShortcutMessages::listElementShownUnread(not_null<const Element*> view) {
  830. return true;
  831. }
  832. bool ShortcutMessages::listIsGoodForAroundPosition(
  833. not_null<const Element*> view) {
  834. return true;
  835. }
  836. void ShortcutMessages::listSendBotCommand(
  837. const QString &command,
  838. const FullMsgId &context) {
  839. }
  840. void ShortcutMessages::listSearch(
  841. const QString &query,
  842. const FullMsgId &context) {
  843. const auto inChat = _history->peer->isUser()
  844. ? Dialogs::Key()
  845. : Dialogs::Key(_history);
  846. _controller->searchMessages(query, inChat);
  847. }
  848. void ShortcutMessages::listHandleViaClick(not_null<UserData*> bot) {
  849. _composeControls->setText({ '@' + bot->username() + ' ' });
  850. }
  851. not_null<Ui::ChatTheme*> ShortcutMessages::listChatTheme() {
  852. return _theme.get();
  853. }
  854. CopyRestrictionType ShortcutMessages::listCopyRestrictionType(
  855. HistoryItem *item) {
  856. return CopyRestrictionType::None;
  857. }
  858. CopyRestrictionType ShortcutMessages::listCopyMediaRestrictionType(
  859. not_null<HistoryItem*> item) {
  860. if (const auto media = item->media()) {
  861. if (const auto invoice = media->invoice()) {
  862. if (!invoice->extendedMedia.empty()) {
  863. return CopyMediaRestrictionTypeFor(_history->peer, item);
  864. }
  865. }
  866. }
  867. return CopyRestrictionType::None;
  868. }
  869. CopyRestrictionType ShortcutMessages::listSelectRestrictionType() {
  870. return CopyRestrictionType::None;
  871. }
  872. auto ShortcutMessages::listAllowedReactionsValue()
  873. -> rpl::producer<Data::AllowedReactions> {
  874. return rpl::single(Data::AllowedReactions());
  875. }
  876. void ShortcutMessages::listShowPremiumToast(
  877. not_null<DocumentData*> document) {
  878. if (!_stickerToast) {
  879. _stickerToast = std::make_unique<HistoryView::StickerToast>(
  880. _controller,
  881. this,
  882. [=] { _stickerToast = nullptr; });
  883. }
  884. _stickerToast->showFor(document);
  885. }
  886. void ShortcutMessages::listOpenPhoto(
  887. not_null<PhotoData*> photo,
  888. FullMsgId context) {
  889. _controller->openPhoto(photo, { context });
  890. }
  891. void ShortcutMessages::listOpenDocument(
  892. not_null<DocumentData*> document,
  893. FullMsgId context,
  894. bool showInMediaView) {
  895. _controller->openDocument(document, showInMediaView, { context });
  896. }
  897. void ShortcutMessages::listPaintEmpty(
  898. Painter &p,
  899. const Ui::ChatPaintContext &context) {
  900. Expects(_emptyIcon != nullptr);
  901. const auto width = st::repliesEmptyWidth;
  902. const auto padding = st::repliesEmptyPadding;
  903. const auto height = padding.top()
  904. + _emptyIcon->height()
  905. + st::repliesEmptySkip
  906. + _emptyTextHeight
  907. + padding.bottom();
  908. const auto r = QRect(
  909. (this->width() - width) / 2,
  910. (this->height() - height) / 3,
  911. width,
  912. height);
  913. HistoryView::ServiceMessagePainter::PaintBubble(p, context.st, r);
  914. _emptyIcon->paint(
  915. p,
  916. r.x() + (r.width() - _emptyIcon->width()) / 2,
  917. r.y() + padding.top(),
  918. this->width());
  919. p.setPen(st::msgServiceFg);
  920. _emptyText.draw(
  921. p,
  922. r.x() + (r.width() - _emptyTextWidth) / 2,
  923. r.y() + padding.top() + _emptyIcon->height() + st::repliesEmptySkip,
  924. _emptyTextWidth,
  925. style::al_top);
  926. }
  927. QString ShortcutMessages::listElementAuthorRank(
  928. not_null<const Element*> view) {
  929. return {};
  930. }
  931. bool ShortcutMessages::listElementHideTopicButton(
  932. not_null<const Element*> view) {
  933. return true;
  934. }
  935. History *ShortcutMessages::listTranslateHistory() {
  936. return nullptr;
  937. }
  938. void ShortcutMessages::listAddTranslatedItems(
  939. not_null<TranslateTracker*> tracker) {
  940. }
  941. bool ShortcutMessages::listIgnorePaintEvent(QWidget *w, QPaintEvent *e) {
  942. return false;
  943. }
  944. void ShortcutMessages::cornerButtonsShowAtPosition(
  945. Data::MessagePosition position) {
  946. showAtPosition(position);
  947. }
  948. Data::Thread *ShortcutMessages::cornerButtonsThread() {
  949. return _history;
  950. }
  951. FullMsgId ShortcutMessages::cornerButtonsCurrentId() {
  952. return _lastShownAt;
  953. }
  954. bool ShortcutMessages::cornerButtonsIgnoreVisibility() {
  955. return false;// animatingShow();
  956. }
  957. std::optional<bool> ShortcutMessages::cornerButtonsDownShown() {
  958. if (_composeControls->isLockPresent()
  959. || _composeControls->isTTLButtonShown()) {
  960. return false;
  961. }
  962. const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
  963. if (top < _scroll->scrollTopMax() || _cornerButtons.replyReturn()) {
  964. return true;
  965. } else if (_inner->loadedAtBottomKnown()) {
  966. return !_inner->loadedAtBottom();
  967. }
  968. return std::nullopt;
  969. }
  970. bool ShortcutMessages::cornerButtonsUnreadMayBeShown() {
  971. return _inner->loadedAtBottomKnown()
  972. && !_composeControls->isLockPresent()
  973. && !_composeControls->isTTLButtonShown();
  974. }
  975. bool ShortcutMessages::cornerButtonsHas(CornerButtonType type) {
  976. return (type == CornerButtonType::Down);
  977. }
  978. void ShortcutMessages::checkReplyReturns() {
  979. const auto currentTop = _scroll->scrollTop();
  980. const auto shortcutId = _shortcutId.current();
  981. while (const auto replyReturn = _cornerButtons.replyReturn()) {
  982. const auto position = replyReturn->position();
  983. const auto scrollTop = _inner->scrollTopForPosition(position);
  984. const auto below = scrollTop
  985. ? (currentTop >= std::min(*scrollTop, _scroll->scrollTopMax()))
  986. : _inner->isBelowPosition(position);
  987. if (replyReturn->shortcutId() != shortcutId || below) {
  988. _cornerButtons.calculateNextReplyReturn();
  989. } else {
  990. break;
  991. }
  992. }
  993. }
  994. void ShortcutMessages::confirmDeleteSelected() {
  995. ConfirmDeleteSelectedItems(_inner);
  996. }
  997. void ShortcutMessages::clearSelected() {
  998. _inner->cancelSelection();
  999. }
  1000. void ShortcutMessages::uploadFile(
  1001. const QByteArray &fileContent,
  1002. SendMediaType type) {
  1003. _session->api().sendFile(fileContent, type, prepareSendAction({}));
  1004. }
  1005. bool ShortcutMessages::showSendingFilesError(
  1006. const Ui::PreparedList &list) const {
  1007. return showSendingFilesError(list, std::nullopt);
  1008. }
  1009. bool ShortcutMessages::showSendingFilesError(
  1010. const Ui::PreparedList &list,
  1011. std::optional<bool> compress) const {
  1012. if (showPremiumRequired()) {
  1013. return true;
  1014. }
  1015. const auto text = [&] {
  1016. using Error = Ui::PreparedList::Error;
  1017. switch (list.error) {
  1018. case Error::None: return QString();
  1019. case Error::EmptyFile:
  1020. case Error::Directory:
  1021. case Error::NonLocalUrl: return tr::lng_send_image_empty(
  1022. tr::now,
  1023. lt_name,
  1024. list.errorData);
  1025. case Error::TooLargeFile: return u"(toolarge)"_q;
  1026. }
  1027. return tr::lng_forward_send_files_cant(tr::now);
  1028. }();
  1029. if (text.isEmpty()) {
  1030. return false;
  1031. } else if (text == u"(toolarge)"_q) {
  1032. const auto fileSize = list.files.back().size;
  1033. _controller->show(
  1034. Box(FileSizeLimitBox, _session, fileSize, nullptr));
  1035. return true;
  1036. }
  1037. _controller->showToast(text);
  1038. return true;
  1039. }
  1040. Api::SendAction ShortcutMessages::prepareSendAction(
  1041. Api::SendOptions options) const {
  1042. auto result = Api::SendAction(_history, options);
  1043. result.replyTo = replyTo();
  1044. result.options.shortcutId = _shortcutId.current();
  1045. result.options.sendAs = _composeControls->sendAsPeer();
  1046. return result;
  1047. }
  1048. void ShortcutMessages::send() {
  1049. if (_composeControls->getTextWithAppliedMarkdown().text.isEmpty()) {
  1050. return;
  1051. }
  1052. send({});
  1053. }
  1054. void ShortcutMessages::sendVoice(ComposeControls::VoiceToSend &&data) {
  1055. if (showPremiumRequired()) {
  1056. return;
  1057. }
  1058. auto action = prepareSendAction(data.options);
  1059. _session->api().sendVoiceMessage(
  1060. data.bytes,
  1061. data.waveform,
  1062. data.duration,
  1063. data.video,
  1064. std::move(action));
  1065. _composeControls->cancelReplyMessage();
  1066. _composeControls->clearListenState();
  1067. finishSending();
  1068. }
  1069. void ShortcutMessages::send(Api::SendOptions options) {
  1070. if (showPremiumRequired()) {
  1071. return;
  1072. }
  1073. _cornerButtons.clearReplyReturns();
  1074. auto message = Api::MessageToSend(prepareSendAction(options));
  1075. message.textWithTags = _composeControls->getTextWithAppliedMarkdown();
  1076. message.webPage = _composeControls->webPageDraft();
  1077. _session->api().sendMessage(std::move(message));
  1078. _composeControls->clear();
  1079. finishSending();
  1080. }
  1081. void ShortcutMessages::edit(
  1082. not_null<HistoryItem*> item,
  1083. Api::SendOptions options,
  1084. mtpRequestId *const saveEditMsgRequestId,
  1085. bool spoilered) {
  1086. if (*saveEditMsgRequestId) {
  1087. return;
  1088. }
  1089. const auto webpage = _composeControls->webPageDraft();
  1090. auto sending = TextWithEntities();
  1091. auto left = _composeControls->prepareTextForEditMsg();
  1092. const auto originalLeftSize = left.text.size();
  1093. const auto hasMediaWithCaption = item
  1094. && item->media()
  1095. && item->media()->allowsEditCaption();
  1096. const auto maxCaptionSize = !hasMediaWithCaption
  1097. ? MaxMessageSize
  1098. : Data::PremiumLimits(_session).captionLengthCurrent();
  1099. if (!TextUtilities::CutPart(sending, left, maxCaptionSize)
  1100. && !hasMediaWithCaption) {
  1101. if (item) {
  1102. _controller->show(Box<DeleteMessagesBox>(item, false));
  1103. } else {
  1104. doSetInnerFocus();
  1105. }
  1106. return;
  1107. } else if (!left.text.isEmpty()) {
  1108. const auto remove = originalLeftSize - maxCaptionSize;
  1109. _controller->showToast(
  1110. tr::lng_edit_limit_reached(tr::now, lt_count, remove));
  1111. return;
  1112. }
  1113. lifetime().add([=] {
  1114. if (!*saveEditMsgRequestId) {
  1115. return;
  1116. }
  1117. _session->api().request(base::take(*saveEditMsgRequestId)).cancel();
  1118. });
  1119. const auto done = [=](mtpRequestId requestId) {
  1120. if (requestId == *saveEditMsgRequestId) {
  1121. *saveEditMsgRequestId = 0;
  1122. _composeControls->cancelEditMessage();
  1123. }
  1124. };
  1125. const auto fail = [=](const QString &error, mtpRequestId requestId) {
  1126. if (requestId == *saveEditMsgRequestId) {
  1127. *saveEditMsgRequestId = 0;
  1128. }
  1129. if (ranges::contains(Api::kDefaultEditMessagesErrors, error)) {
  1130. _controller->showToast(tr::lng_edit_error(tr::now));
  1131. } else if (error == u"MESSAGE_NOT_MODIFIED"_q) {
  1132. _composeControls->cancelEditMessage();
  1133. } else if (error == u"MESSAGE_EMPTY"_q) {
  1134. doSetInnerFocus();
  1135. } else {
  1136. _controller->showToast(tr::lng_edit_error(tr::now));
  1137. }
  1138. update();
  1139. return true;
  1140. };
  1141. *saveEditMsgRequestId = Api::EditTextMessage(
  1142. item,
  1143. sending,
  1144. webpage,
  1145. options,
  1146. crl::guard(this, done),
  1147. crl::guard(this, fail),
  1148. spoilered);
  1149. _composeControls->hidePanelsAnimated();
  1150. doSetInnerFocus();
  1151. }
  1152. bool ShortcutMessages::confirmSendingFiles(
  1153. not_null<const QMimeData*> data,
  1154. std::optional<bool> overrideSendImagesAsPhotos,
  1155. const QString &insertTextOnCancel) {
  1156. const auto hasImage = data->hasImage();
  1157. const auto premium = _controller->session().user()->isPremium();
  1158. if (const auto urls = Core::ReadMimeUrls(data); !urls.empty()) {
  1159. auto list = Storage::PrepareMediaList(
  1160. urls,
  1161. st::sendMediaPreviewSize,
  1162. premium);
  1163. if (list.error != Ui::PreparedList::Error::NonLocalUrl) {
  1164. if (list.error == Ui::PreparedList::Error::None
  1165. || !hasImage) {
  1166. const auto emptyTextOnCancel = QString();
  1167. list.overrideSendImagesAsPhotos = overrideSendImagesAsPhotos;
  1168. confirmSendingFiles(std::move(list), emptyTextOnCancel);
  1169. return true;
  1170. }
  1171. }
  1172. }
  1173. if (auto read = Core::ReadMimeImage(data)) {
  1174. confirmSendingFiles(
  1175. std::move(read.image),
  1176. std::move(read.content),
  1177. overrideSendImagesAsPhotos,
  1178. insertTextOnCancel);
  1179. return true;
  1180. }
  1181. return false;
  1182. }
  1183. bool ShortcutMessages::confirmSendingFiles(
  1184. Ui::PreparedList &&list,
  1185. const QString &insertTextOnCancel) {
  1186. if (_composeControls->confirmMediaEdit(list)) {
  1187. return true;
  1188. } else if (showSendingFilesError(list)) {
  1189. return false;
  1190. }
  1191. auto box = Box<SendFilesBox>(
  1192. _controller,
  1193. std::move(list),
  1194. _composeControls->getTextWithAppliedMarkdown(),
  1195. _history->peer,
  1196. Api::SendType::Normal,
  1197. SendMenu::Details());
  1198. box->setConfirmedCallback(crl::guard(this, [=](
  1199. Ui::PreparedList &&list,
  1200. Ui::SendFilesWay way,
  1201. TextWithTags &&caption,
  1202. Api::SendOptions options,
  1203. bool ctrlShiftEnter) {
  1204. sendingFilesConfirmed(
  1205. std::move(list),
  1206. way,
  1207. std::move(caption),
  1208. options,
  1209. ctrlShiftEnter);
  1210. }));
  1211. box->setCancelledCallback(_composeControls->restoreTextCallback(
  1212. insertTextOnCancel));
  1213. //ActivateWindow(_controller);
  1214. _controller->show(std::move(box));
  1215. return true;
  1216. }
  1217. bool ShortcutMessages::confirmSendingFiles(
  1218. QImage &&image,
  1219. QByteArray &&content,
  1220. std::optional<bool> overrideSendImagesAsPhotos,
  1221. const QString &insertTextOnCancel) {
  1222. if (image.isNull()) {
  1223. return false;
  1224. }
  1225. auto list = Storage::PrepareMediaFromImage(
  1226. std::move(image),
  1227. std::move(content),
  1228. st::sendMediaPreviewSize);
  1229. list.overrideSendImagesAsPhotos = overrideSendImagesAsPhotos;
  1230. return confirmSendingFiles(std::move(list), insertTextOnCancel);
  1231. }
  1232. void ShortcutMessages::sendingFilesConfirmed(
  1233. Ui::PreparedList &&list,
  1234. Ui::SendFilesWay way,
  1235. TextWithTags &&caption,
  1236. Api::SendOptions options,
  1237. bool ctrlShiftEnter) {
  1238. Expects(list.filesToProcess.empty());
  1239. if (showSendingFilesError(list, way.sendImagesAsPhotos())) {
  1240. return;
  1241. }
  1242. auto groups = DivideByGroups(
  1243. std::move(list),
  1244. way,
  1245. _history->peer->slowmodeApplied());
  1246. const auto type = way.sendImagesAsPhotos()
  1247. ? SendMediaType::Photo
  1248. : SendMediaType::File;
  1249. auto action = prepareSendAction(options);
  1250. action.clearDraft = false;
  1251. if ((groups.size() != 1 || !groups.front().sentWithCaption())
  1252. && !caption.text.isEmpty()) {
  1253. auto message = Api::MessageToSend(action);
  1254. message.textWithTags = base::take(caption);
  1255. _session->api().sendMessage(std::move(message));
  1256. }
  1257. for (auto &group : groups) {
  1258. const auto album = (group.type != Ui::AlbumType::None)
  1259. ? std::make_shared<SendingAlbum>()
  1260. : nullptr;
  1261. _session->api().sendFiles(
  1262. std::move(group.list),
  1263. type,
  1264. base::take(caption),
  1265. album,
  1266. action);
  1267. }
  1268. if (_composeControls->replyingToMessage() == action.replyTo) {
  1269. _composeControls->cancelReplyMessage();
  1270. }
  1271. finishSending();
  1272. }
  1273. void ShortcutMessages::chooseAttach(
  1274. std::optional<bool> overrideSendImagesAsPhotos) {
  1275. if (showPremiumRequired()) {
  1276. return;
  1277. }
  1278. _choosingAttach = false;
  1279. const auto filter = (overrideSendImagesAsPhotos == true)
  1280. ? FileDialog::PhotoVideoFilesFilter()
  1281. : FileDialog::AllOrImagesFilter();
  1282. FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=](
  1283. FileDialog::OpenResult &&result) {
  1284. if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
  1285. return;
  1286. }
  1287. if (!result.remoteContent.isEmpty()) {
  1288. auto read = Images::Read({
  1289. .content = result.remoteContent,
  1290. });
  1291. if (!read.image.isNull() && !read.animated) {
  1292. confirmSendingFiles(
  1293. std::move(read.image),
  1294. std::move(result.remoteContent),
  1295. overrideSendImagesAsPhotos);
  1296. } else {
  1297. uploadFile(result.remoteContent, SendMediaType::File);
  1298. }
  1299. } else {
  1300. const auto premium = _controller->session().user()->isPremium();
  1301. auto list = Storage::PrepareMediaList(
  1302. result.paths,
  1303. st::sendMediaPreviewSize,
  1304. premium);
  1305. list.overrideSendImagesAsPhotos = overrideSendImagesAsPhotos;
  1306. confirmSendingFiles(std::move(list));
  1307. }
  1308. }), nullptr);
  1309. }
  1310. void ShortcutMessages::finishSending() {
  1311. _composeControls->hidePanelsAnimated();
  1312. //if (_previewData && _previewData->pendingTill) previewCancel();
  1313. doSetInnerFocus();
  1314. showAtEnd();
  1315. }
  1316. void ShortcutMessages::showAtEnd() {
  1317. showAtPosition(Data::MaxMessagePosition);
  1318. }
  1319. void ShortcutMessages::doSetInnerFocus() {
  1320. if (!_inner->getSelectedText().rich.text.isEmpty()
  1321. || !_inner->getSelectedItems().empty()
  1322. || !_composeControls->focus()) {
  1323. _inner->setFocus();
  1324. }
  1325. }
  1326. bool ShortcutMessages::sendExistingDocument(
  1327. not_null<DocumentData*> document,
  1328. Api::SendOptions options,
  1329. std::optional<MsgId> localId) {
  1330. if (showPremiumRequired()) {
  1331. return false;
  1332. }
  1333. Api::SendExistingDocument(
  1334. Api::MessageToSend(prepareSendAction(options)),
  1335. document,
  1336. localId);
  1337. _composeControls->cancelReplyMessage();
  1338. finishSending();
  1339. return true;
  1340. }
  1341. void ShortcutMessages::sendExistingPhoto(not_null<PhotoData*> photo) {
  1342. sendExistingPhoto(photo, {});
  1343. }
  1344. bool ShortcutMessages::sendExistingPhoto(
  1345. not_null<PhotoData*> photo,
  1346. Api::SendOptions options) {
  1347. if (showPremiumRequired()) {
  1348. return false;
  1349. }
  1350. Api::SendExistingPhoto(
  1351. Api::MessageToSend(prepareSendAction(options)),
  1352. photo);
  1353. _composeControls->cancelReplyMessage();
  1354. finishSending();
  1355. return true;
  1356. }
  1357. void ShortcutMessages::sendInlineResult(
  1358. std::shared_ptr<InlineBots::Result> result,
  1359. not_null<UserData*> bot) {
  1360. if (showPremiumRequired()) {
  1361. return;
  1362. } else if (const auto error = result->getErrorOnSend(_history)) {
  1363. Data::ShowSendErrorToast(_controller, _history->peer, error);
  1364. return;
  1365. }
  1366. sendInlineResult(std::move(result), bot, {}, std::nullopt);
  1367. }
  1368. void ShortcutMessages::sendInlineResult(
  1369. std::shared_ptr<InlineBots::Result> result,
  1370. not_null<UserData*> bot,
  1371. Api::SendOptions options,
  1372. std::optional<MsgId> localMessageId) {
  1373. if (showPremiumRequired()) {
  1374. return;
  1375. }
  1376. auto action = prepareSendAction(options);
  1377. action.generateLocal = true;
  1378. _session->api().sendInlineResult(
  1379. bot,
  1380. result.get(),
  1381. action,
  1382. localMessageId);
  1383. _composeControls->clear();
  1384. //_saveDraftText = true;
  1385. //_saveDraftStart = crl::now();
  1386. //onDraftSave();
  1387. auto &bots = cRefRecentInlineBots();
  1388. const auto index = bots.indexOf(bot);
  1389. if (index) {
  1390. if (index > 0) {
  1391. bots.removeAt(index);
  1392. } else if (bots.size() >= RecentInlineBotsLimit) {
  1393. bots.resize(RecentInlineBotsLimit - 1);
  1394. }
  1395. bots.push_front(bot);
  1396. bot->session().local().writeRecentHashtagsAndBots();
  1397. }
  1398. finishSending();
  1399. }
  1400. void ShortcutMessages::showAtPosition(
  1401. Data::MessagePosition position,
  1402. FullMsgId originItemId) {
  1403. showAtPosition(position, originItemId, {});
  1404. }
  1405. void ShortcutMessages::showAtPosition(
  1406. Data::MessagePosition position,
  1407. FullMsgId originItemId,
  1408. const Window::SectionShow &params) {
  1409. _lastShownAt = position.fullId;
  1410. _inner->showAtPosition(
  1411. position,
  1412. params,
  1413. _cornerButtons.doneJumpFrom(position.fullId, originItemId, true));
  1414. }
  1415. FullReplyTo ShortcutMessages::replyTo() const {
  1416. return _composeControls->replyingToMessage();
  1417. }
  1418. bool ShortcutMessages::showPremiumRequired() const {
  1419. if (!_controller->session().premium()) {
  1420. ShowPremiumPreviewToBuy(_controller, PremiumFeature::QuickReplies);
  1421. return true;
  1422. }
  1423. return false;
  1424. }
  1425. } // namespace
  1426. Type ShortcutMessagesId(int shortcutId) {
  1427. return ShortcutMessages::Id(shortcutId);
  1428. }
  1429. } // namespace Settings