send_files_box.cpp 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "boxes/send_files_box.h"
  8. #include "lang/lang_keys.h"
  9. #include "storage/localstorage.h"
  10. #include "storage/storage_media_prepare.h"
  11. #include "iv/iv_instance.h"
  12. #include "mainwidget.h"
  13. #include "main/main_app_config.h"
  14. #include "main/main_session.h"
  15. #include "main/main_session_settings.h"
  16. #include "mtproto/mtproto_config.h"
  17. #include "chat_helpers/message_field.h"
  18. #include "menu/menu_send.h"
  19. #include "chat_helpers/emoji_suggestions_widget.h"
  20. #include "chat_helpers/field_autocomplete.h"
  21. #include "chat_helpers/tabbed_panel.h"
  22. #include "chat_helpers/tabbed_selector.h"
  23. #include "editor/photo_editor_layer_widget.h"
  24. #include "history/history_drag_area.h"
  25. #include "history/view/controls/history_view_characters_limit.h"
  26. #include "history/view/history_view_schedule_box.h"
  27. #include "core/mime_type.h"
  28. #include "core/ui_integration.h"
  29. #include "base/event_filter.h"
  30. #include "base/call_delayed.h"
  31. #include "boxes/premium_limits_box.h"
  32. #include "boxes/premium_preview_box.h"
  33. #include "boxes/send_credits_box.h"
  34. #include "ui/effects/scroll_content_shadow.h"
  35. #include "ui/widgets/fields/number_input.h"
  36. #include "ui/widgets/checkbox.h"
  37. #include "ui/widgets/scroll_area.h"
  38. #include "ui/widgets/popup_menu.h"
  39. #include "ui/chat/attach/attach_album_preview.h"
  40. #include "ui/chat/attach/attach_single_file_preview.h"
  41. #include "ui/chat/attach/attach_single_media_preview.h"
  42. #include "ui/grouped_layout.h"
  43. #include "ui/text/text_utilities.h"
  44. #include "ui/toast/toast.h"
  45. #include "ui/controls/emoji_button.h"
  46. #include "ui/painter.h"
  47. #include "ui/vertical_list.h"
  48. #include "ui/ui_utility.h"
  49. #include "lottie/lottie_single_player.h"
  50. #include "data/data_channel.h"
  51. #include "data/data_document.h"
  52. #include "data/data_user.h"
  53. #include "data/data_peer_values.h" // Data::AmPremiumValue.
  54. #include "data/data_premium_limits.h"
  55. #include "data/stickers/data_stickers.h"
  56. #include "data/stickers/data_custom_emoji.h"
  57. #include "window/window_session_controller.h"
  58. #include "core/application.h"
  59. #include "core/core_settings.h"
  60. #include "styles/style_boxes.h"
  61. #include "styles/style_chat_helpers.h"
  62. #include "styles/style_layers.h"
  63. #include <QtCore/QMimeData>
  64. namespace {
  65. constexpr auto kMaxMessageLength = 4096;
  66. using Ui::SendFilesWay;
  67. [[nodiscard]] inline bool CanAddUrls(const QList<QUrl> &urls) {
  68. return !urls.isEmpty() && ranges::all_of(urls, &QUrl::isLocalFile);
  69. }
  70. [[nodiscard]] bool CanAddFiles(not_null<const QMimeData*> data) {
  71. return data->hasImage() || CanAddUrls(Core::ReadMimeUrls(data));
  72. }
  73. void FileDialogCallback(
  74. FileDialog::OpenResult &&result,
  75. Fn<bool(const Ui::PreparedList&)> checkResult,
  76. Fn<void(Ui::PreparedList)> callback,
  77. bool premium,
  78. std::shared_ptr<Ui::Show> show) {
  79. auto showError = [=](tr::phrase<> text) {
  80. show->showToast(text(tr::now));
  81. };
  82. auto list = Storage::PreparedFileFromFilesDialog(
  83. std::move(result),
  84. checkResult,
  85. showError,
  86. st::sendMediaPreviewSize,
  87. premium);
  88. if (!list) {
  89. return;
  90. }
  91. callback(std::move(*list));
  92. }
  93. rpl::producer<QString> FieldPlaceholder(
  94. const Ui::PreparedList &list,
  95. SendFilesWay way) {
  96. return list.canAddCaption(
  97. way.groupFiles() && way.sendImagesAsPhotos(),
  98. way.sendImagesAsPhotos())
  99. ? tr::lng_photo_caption()
  100. : tr::lng_photos_comment();
  101. }
  102. void EditPriceBox(
  103. not_null<Ui::GenericBox*> box,
  104. not_null<Main::Session*> session,
  105. uint64 price,
  106. Fn<void(uint64)> apply) {
  107. box->setTitle(tr::lng_paid_title());
  108. AddSubsectionTitle(
  109. box->verticalLayout(),
  110. tr::lng_paid_enter_cost(),
  111. (st::boxRowPadding - QMargins(
  112. st::defaultSubsectionTitlePadding.left(),
  113. 0,
  114. st::defaultSubsectionTitlePadding.right(),
  115. 0)));
  116. const auto limit = session->appConfig().get<int>(
  117. u"stars_paid_post_amount_max"_q,
  118. 10'000);
  119. const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>(
  120. box,
  121. st::editTagField.heightMin));
  122. auto owned = object_ptr<Ui::NumberInput>(
  123. wrap,
  124. st::editTagField,
  125. tr::lng_paid_cost_placeholder(),
  126. price ? QString::number(price) : QString(),
  127. limit);
  128. const auto field = owned.data();
  129. wrap->widthValue() | rpl::start_with_next([=](int width) {
  130. field->move(0, 0);
  131. field->resize(width, field->height());
  132. wrap->resize(width, field->height());
  133. }, wrap->lifetime());
  134. field->selectAll();
  135. box->setFocusCallback([=] {
  136. field->setFocusFast();
  137. });
  138. const auto about = box->addRow(
  139. object_ptr<Ui::FlatLabel>(
  140. box,
  141. tr::lng_paid_about(
  142. lt_link,
  143. tr::lng_paid_about_link() | Ui::Text::ToLink(),
  144. Ui::Text::WithEntities),
  145. st::paidAmountAbout),
  146. st::boxRowPadding + QMargins(0, st::sendMediaRowSkip, 0, 0));
  147. about->setClickHandlerFilter([=](const auto &...) {
  148. Core::App().iv().openWithIvPreferred(
  149. session,
  150. tr::lng_paid_about_link_url(tr::now));
  151. return false;
  152. });
  153. field->paintRequest() | rpl::start_with_next([=](QRect clip) {
  154. auto p = QPainter(field);
  155. st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width());
  156. }, field->lifetime());
  157. const auto save = [=] {
  158. const auto now = field->getLastText().toULongLong();
  159. if (now > limit) {
  160. field->showError();
  161. return;
  162. }
  163. const auto weak = Ui::MakeWeak(box);
  164. apply(now);
  165. if (const auto strong = weak.data()) {
  166. strong->closeBox();
  167. }
  168. };
  169. QObject::connect(field, &Ui::NumberInput::submitted, box, save);
  170. box->addButton(tr::lng_settings_save(), save);
  171. box->addButton(tr::lng_cancel(), [=] {
  172. box->closeBox();
  173. });
  174. }
  175. } // namespace
  176. SendFilesLimits DefaultLimitsForPeer(not_null<PeerData*> peer) {
  177. using Flag = SendFilesAllow;
  178. using Restriction = ChatRestriction;
  179. const auto allowByRestriction = [&](Restriction check, Flag allow) {
  180. return Data::RestrictionError(peer, check) ? Flag() : allow;
  181. };
  182. return Flag()
  183. | (peer->slowmodeApplied() ? Flag::OnlyOne : Flag())
  184. | (Data::AllowEmojiWithoutPremium(peer)
  185. ? Flag::EmojiWithoutPremium
  186. : Flag())
  187. | allowByRestriction(Restriction::SendPhotos, Flag::Photos)
  188. | allowByRestriction(Restriction::SendVideos, Flag::Videos)
  189. | allowByRestriction(Restriction::SendMusic, Flag::Music)
  190. | allowByRestriction(Restriction::SendFiles, Flag::Files)
  191. | allowByRestriction(Restriction::SendStickers, Flag::Stickers)
  192. | allowByRestriction(Restriction::SendGifs, Flag::Gifs)
  193. | allowByRestriction(Restriction::SendOther, Flag::Texts);
  194. }
  195. SendFilesCheck DefaultCheckForPeer(
  196. not_null<Window::SessionController*> controller,
  197. not_null<PeerData*> peer) {
  198. return DefaultCheckForPeer(controller->uiShow(), peer);
  199. }
  200. SendFilesCheck DefaultCheckForPeer(
  201. std::shared_ptr<ChatHelpers::Show> show,
  202. not_null<PeerData*> peer) {
  203. return [=](
  204. const Ui::PreparedFile &file,
  205. bool compress,
  206. bool silent) {
  207. const auto error = Data::FileRestrictionError(peer, file, compress);
  208. if (error && !silent) {
  209. Data::ShowSendErrorToast(show, peer, error);
  210. }
  211. return !error.has_value();
  212. };
  213. }
  214. SendFilesBox::Block::Block(
  215. not_null<QWidget*> parent,
  216. const style::ComposeControls &st,
  217. not_null<std::vector<Ui::PreparedFile>*> items,
  218. int from,
  219. int till,
  220. Fn<bool()> gifPaused,
  221. SendFilesWay way,
  222. Fn<bool(const Ui::PreparedFile &, Ui::AttachActionType)> actionAllowed)
  223. : _items(items)
  224. , _from(from)
  225. , _till(till) {
  226. Expects(from >= 0);
  227. Expects(till > from);
  228. Expects(till <= items->size());
  229. const auto count = till - from;
  230. const auto my = gsl::make_span(*items).subspan(from, count);
  231. const auto &first = my.front();
  232. _isAlbum = (my.size() > 1);
  233. if (_isAlbum) {
  234. const auto preview = Ui::CreateChild<Ui::AlbumPreview>(
  235. parent.get(),
  236. st,
  237. my,
  238. way,
  239. [=](int index, Ui::AttachActionType type) {
  240. return actionAllowed((*_items)[from + index], type);
  241. });
  242. _preview.reset(preview);
  243. } else {
  244. const auto media = Ui::SingleMediaPreview::Create(
  245. parent,
  246. st,
  247. gifPaused,
  248. first,
  249. [=](Ui::AttachActionType type) {
  250. return actionAllowed((*_items)[from], type);
  251. });
  252. if (media) {
  253. _isSingleMedia = true;
  254. _preview.reset(media);
  255. } else {
  256. _preview.reset(Ui::CreateChild<Ui::SingleFilePreview>(
  257. parent.get(),
  258. st,
  259. first));
  260. }
  261. }
  262. _preview->show();
  263. }
  264. int SendFilesBox::Block::fromIndex() const {
  265. return _from;
  266. }
  267. int SendFilesBox::Block::tillIndex() const {
  268. return _till;
  269. }
  270. object_ptr<Ui::RpWidget> SendFilesBox::Block::takeWidget() {
  271. return object_ptr<Ui::RpWidget>::fromRaw(_preview.get());
  272. }
  273. rpl::producer<int> SendFilesBox::Block::itemDeleteRequest() const {
  274. using namespace rpl::mappers;
  275. const auto preview = _preview.get();
  276. const auto from = _from;
  277. if (_isAlbum) {
  278. const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
  279. return album->thumbDeleted() | rpl::map(_1 + from);
  280. } else if (_isSingleMedia) {
  281. const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
  282. return media->deleteRequests() | rpl::map([from] { return from; });
  283. } else {
  284. const auto single = static_cast<Ui::SingleFilePreview*>(preview);
  285. return single->deleteRequests() | rpl::map([from] { return from; });
  286. }
  287. }
  288. rpl::producer<int> SendFilesBox::Block::itemReplaceRequest() const {
  289. using namespace rpl::mappers;
  290. const auto preview = _preview.get();
  291. const auto from = _from;
  292. if (_isAlbum) {
  293. const auto album = static_cast<Ui::AlbumPreview*>(preview);
  294. return album->thumbChanged() | rpl::map(_1 + from);
  295. } else if (_isSingleMedia) {
  296. const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
  297. return media->editRequests() | rpl::map([from] { return from; });
  298. } else {
  299. const auto single = static_cast<Ui::SingleFilePreview*>(preview);
  300. return single->editRequests() | rpl::map([from] { return from; });
  301. }
  302. }
  303. rpl::producer<int> SendFilesBox::Block::itemModifyRequest() const {
  304. using namespace rpl::mappers;
  305. const auto preview = _preview.get();
  306. const auto from = _from;
  307. if (_isAlbum) {
  308. const auto album = static_cast<Ui::AlbumPreview*>(preview);
  309. return album->thumbModified() | rpl::map(_1 + from);
  310. } else if (_isSingleMedia) {
  311. const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
  312. return media->modifyRequests() | rpl::map_to(from);
  313. } else {
  314. return rpl::never<int>();
  315. }
  316. }
  317. rpl::producer<int> SendFilesBox::Block::itemEditCoverRequest() const {
  318. using namespace rpl::mappers;
  319. const auto preview = _preview.get();
  320. const auto from = _from;
  321. if (_isAlbum) {
  322. const auto album = static_cast<Ui::AlbumPreview*>(preview);
  323. return album->thumbEditCoverRequested() | rpl::map(_1 + from);
  324. } else if (_isSingleMedia) {
  325. const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
  326. return media->editCoverRequests() | rpl::map_to(from);
  327. } else {
  328. return rpl::never<int>();
  329. }
  330. }
  331. rpl::producer<int> SendFilesBox::Block::itemClearCoverRequest() const {
  332. using namespace rpl::mappers;
  333. const auto preview = _preview.get();
  334. const auto from = _from;
  335. if (_isAlbum) {
  336. const auto album = static_cast<Ui::AlbumPreview*>(preview);
  337. return album->thumbClearCoverRequested() | rpl::map(_1 + from);
  338. } else if (_isSingleMedia) {
  339. const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
  340. return media->clearCoverRequests() | rpl::map_to(from);
  341. } else {
  342. return rpl::never<int>();
  343. }
  344. }
  345. rpl::producer<> SendFilesBox::Block::orderUpdated() const {
  346. if (_isAlbum) {
  347. const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
  348. return album->orderUpdated();
  349. }
  350. return rpl::never<>();
  351. }
  352. void SendFilesBox::Block::setSendWay(Ui::SendFilesWay way) {
  353. if (!_isAlbum) {
  354. if (_isSingleMedia) {
  355. const auto media = static_cast<Ui::SingleMediaPreview*>(
  356. _preview.get());
  357. media->setSendWay(way);
  358. }
  359. return;
  360. }
  361. applyChanges();
  362. const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
  363. album->setSendWay(way);
  364. }
  365. void SendFilesBox::Block::toggleSpoilers(bool enabled) {
  366. if (_isAlbum) {
  367. const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
  368. album->toggleSpoilers(enabled);
  369. } else if (_isSingleMedia) {
  370. const auto media = static_cast<Ui::SingleMediaPreview*>(
  371. _preview.get());
  372. media->setSpoiler(enabled);
  373. }
  374. }
  375. void SendFilesBox::Block::applyChanges() {
  376. if (!_isAlbum) {
  377. if (_isSingleMedia) {
  378. const auto media = static_cast<Ui::SingleMediaPreview*>(
  379. _preview.get());
  380. if (media->canHaveSpoiler()) {
  381. (*_items)[_from].spoiler = media->hasSpoiler();
  382. }
  383. }
  384. return;
  385. }
  386. const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
  387. const auto order = album->takeOrder();
  388. const auto guard = gsl::finally([&] {
  389. const auto spoilered = album->collectSpoileredIndices();
  390. for (auto i = 0, count = int(order.size()); i != count; ++i) {
  391. if (album->canHaveSpoiler(i)) {
  392. (*_items)[_from + i].spoiler = spoilered.contains(i);
  393. }
  394. }
  395. });
  396. const auto isIdentity = [&] {
  397. for (auto i = 0, count = int(order.size()); i != count; ++i) {
  398. if (order[i] != i) {
  399. return false;
  400. }
  401. }
  402. return true;
  403. }();
  404. if (isIdentity) {
  405. return;
  406. }
  407. auto elements = std::vector<Ui::PreparedFile>();
  408. elements.reserve(order.size());
  409. for (const auto index : order) {
  410. elements.push_back(std::move((*_items)[_from + index]));
  411. }
  412. for (auto i = 0, count = int(order.size()); i != count; ++i) {
  413. (*_items)[_from + i] = std::move(elements[i]);
  414. }
  415. }
  416. QImage SendFilesBox::Block::generatePriceTagBackground() const {
  417. const auto preview = _preview.get();
  418. if (_isAlbum) {
  419. const auto album = static_cast<Ui::AlbumPreview*>(preview);
  420. return album->generatePriceTagBackground();
  421. } else if (_isSingleMedia) {
  422. const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
  423. return media->generatePriceTagBackground();
  424. }
  425. return QImage();
  426. }
  427. SendFilesBox::SendFilesBox(
  428. QWidget*,
  429. not_null<Window::SessionController*> controller,
  430. Ui::PreparedList &&list,
  431. const TextWithTags &caption,
  432. not_null<PeerData*> toPeer,
  433. Api::SendType sendType,
  434. SendMenu::Details sendMenuDetails)
  435. : SendFilesBox(nullptr, {
  436. .show = controller->uiShow(),
  437. .list = std::move(list),
  438. .caption = caption,
  439. .captionToPeer = toPeer,
  440. .limits = DefaultLimitsForPeer(toPeer),
  441. .check = DefaultCheckForPeer(controller, toPeer),
  442. .sendType = sendType,
  443. .sendMenuDetails = [=] { return sendMenuDetails; },
  444. }) {
  445. }
  446. SendFilesBox::SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor)
  447. : _show(std::move(descriptor.show))
  448. , _st(descriptor.stOverride
  449. ? *descriptor.stOverride
  450. : st::defaultComposeControls)
  451. , _sendType(descriptor.sendType)
  452. , _titleHeight(st::boxTitleHeight)
  453. , _list(std::move(descriptor.list))
  454. , _limits(descriptor.limits)
  455. , _sendMenuDetails(prepareSendMenuDetails(descriptor))
  456. , _sendMenuCallback(prepareSendMenuCallback())
  457. , _captionToPeer(descriptor.captionToPeer)
  458. , _check(std::move(descriptor.check))
  459. , _confirmedCallback(std::move(descriptor.confirmed))
  460. , _cancelledCallback(std::move(descriptor.cancelled))
  461. , _caption(this, _st.files.caption, Ui::InputField::Mode::MultiLine)
  462. , _prefilledCaptionText(std::move(descriptor.caption))
  463. , _scroll(this, st::boxScroll)
  464. , _inner(
  465. _scroll->setOwnedWidget(
  466. object_ptr<Ui::VerticalLayout>(_scroll.data()))) {
  467. enqueueNextPrepare();
  468. }
  469. Fn<SendMenu::Details()> SendFilesBox::prepareSendMenuDetails(
  470. const SendFilesBoxDescriptor &descriptor) {
  471. auto initial = descriptor.sendMenuDetails;
  472. return crl::guard(this, [=] {
  473. auto result = initial ? initial() : SendMenu::Details();
  474. result.spoiler = !hasSpoilerMenu()
  475. ? SendMenu::SpoilerState::None
  476. : allWithSpoilers()
  477. ? SendMenu::SpoilerState::Enabled
  478. : SendMenu::SpoilerState::Possible;
  479. const auto way = _sendWay.current();
  480. const auto canMoveCaption = _list.canMoveCaption(
  481. way.groupFiles() && way.sendImagesAsPhotos(),
  482. way.sendImagesAsPhotos()
  483. ) && _caption && HasSendText(_caption);
  484. result.caption = !canMoveCaption
  485. ? SendMenu::CaptionState::None
  486. : _invertCaption
  487. ? SendMenu::CaptionState::Above
  488. : SendMenu::CaptionState::Below;
  489. result.price = canChangePrice()
  490. ? _price.current()
  491. : std::optional<uint64>();
  492. return result;
  493. });
  494. }
  495. auto SendFilesBox::prepareSendMenuCallback()
  496. -> Fn<void(MenuAction, MenuDetails)> {
  497. return crl::guard(this, [=](MenuAction action, MenuDetails details) {
  498. using Type = SendMenu::ActionType;
  499. switch (action.type) {
  500. case Type::CaptionDown: _invertCaption = false; break;
  501. case Type::CaptionUp: _invertCaption = true; break;
  502. case Type::SpoilerOn: toggleSpoilers(true); break;
  503. case Type::SpoilerOff: toggleSpoilers(false); break;
  504. case Type::ChangePrice: changePrice(); break;
  505. default:
  506. SendMenu::DefaultCallback(
  507. _show,
  508. sendCallback())(
  509. action,
  510. details);
  511. break;
  512. }
  513. });
  514. }
  515. void SendFilesBox::initPreview() {
  516. using namespace rpl::mappers;
  517. refreshControls(true);
  518. updateBoxSize();
  519. _dimensionsLifetime.destroy();
  520. _inner->resizeToWidth(st::boxWideWidth);
  521. rpl::combine(
  522. _inner->heightValue(),
  523. _footerHeight.value(),
  524. _titleHeight.value(),
  525. _1 + _2 + _3
  526. ) | rpl::start_with_next([=](int height) {
  527. setDimensions(
  528. st::boxWideWidth,
  529. std::min(st::sendMediaPreviewHeightMax, height),
  530. true);
  531. }, _dimensionsLifetime);
  532. }
  533. void SendFilesBox::enqueueNextPrepare() {
  534. if (_preparing) {
  535. return;
  536. }
  537. while (!_list.filesToProcess.empty()
  538. && _list.filesToProcess.front().information) {
  539. auto file = std::move(_list.filesToProcess.front());
  540. _list.filesToProcess.pop_front();
  541. addFile(std::move(file));
  542. }
  543. if (_list.filesToProcess.empty()) {
  544. return;
  545. }
  546. auto file = std::move(_list.filesToProcess.front());
  547. _list.filesToProcess.pop_front();
  548. const auto weak = Ui::MakeWeak(this);
  549. _preparing = true;
  550. const auto sideLimit = PhotoSideLimit(); // Get on main thread.
  551. crl::async([weak, sideLimit, file = std::move(file)]() mutable {
  552. Storage::PrepareDetails(file, st::sendMediaPreviewSize, sideLimit);
  553. crl::on_main([weak, file = std::move(file)]() mutable {
  554. if (weak) {
  555. weak->addPreparedAsyncFile(std::move(file));
  556. }
  557. });
  558. });
  559. }
  560. void SendFilesBox::prepare() {
  561. initSendWay();
  562. setupCaption();
  563. setupSendWayControls();
  564. preparePreview();
  565. initPreview();
  566. SetupShadowsToScrollContent(this, _scroll, _inner->heightValue());
  567. setCloseByOutsideClick(false);
  568. boxClosing() | rpl::start_with_next([=] {
  569. if (!_confirmed && _cancelledCallback) {
  570. _cancelledCallback();
  571. }
  572. }, lifetime());
  573. setupDragArea();
  574. }
  575. void SendFilesBox::setupDragArea() {
  576. // Avoid both drag areas appearing at one time.
  577. auto computeState = [=](const QMimeData *data) {
  578. using DragState = Storage::MimeDataState;
  579. const auto state = Storage::ComputeMimeDataState(data);
  580. return (state == DragState::PhotoFiles || state == DragState::Image)
  581. ? (_sendWay.current().sendImagesAsPhotos()
  582. ? DragState::Image
  583. : DragState::Files)
  584. : state;
  585. };
  586. const auto areas = DragArea::SetupDragAreaToContainer(
  587. this,
  588. CanAddFiles,
  589. [=](bool f) { _caption->setAcceptDrops(f); },
  590. [=] { updateControlsGeometry(); },
  591. std::move(computeState));
  592. const auto droppedCallback = [=](bool compress) {
  593. return [=](const QMimeData *data) {
  594. addFiles(data);
  595. _show->activate();
  596. };
  597. };
  598. areas.document->setDroppedCallback(droppedCallback(false));
  599. areas.photo->setDroppedCallback(droppedCallback(true));
  600. }
  601. void SendFilesBox::refreshAllAfterChanges(int fromItem, Fn<void()> perform) {
  602. auto fromBlock = 0;
  603. for (auto count = int(_blocks.size()); fromBlock != count; ++fromBlock) {
  604. if (_blocks[fromBlock].tillIndex() >= fromItem) {
  605. break;
  606. }
  607. }
  608. for (auto index = fromBlock; index < _blocks.size(); ++index) {
  609. _blocks[index].applyChanges();
  610. }
  611. if (perform) {
  612. perform();
  613. }
  614. generatePreviewFrom(fromBlock);
  615. {
  616. auto sendWay = _sendWay.current();
  617. sendWay.setHasCompressedStickers(_list.hasSticker());
  618. if (_limits & SendFilesAllow::OnlyOne) {
  619. if (_list.files.size() > 1) {
  620. sendWay.setGroupFiles(true);
  621. }
  622. }
  623. _sendWay = sendWay;
  624. }
  625. _inner->resizeToWidth(st::boxWideWidth);
  626. refreshControls();
  627. captionResized();
  628. }
  629. void SendFilesBox::openDialogToAddFileToAlbum() {
  630. const auto show = uiShow();
  631. const auto checkResult = [=](const Ui::PreparedList &list) {
  632. if (!(_limits & SendFilesAllow::OnlyOne)) {
  633. return true;
  634. } else if (!_list.canBeSentInSlowmodeWith(list)) {
  635. showToast(tr::lng_slowmode_no_many(tr::now));
  636. return false;
  637. }
  638. return true;
  639. };
  640. const auto callback = [=](FileDialog::OpenResult &&result) {
  641. const auto premium = _show->session().premium();
  642. FileDialogCallback(
  643. std::move(result),
  644. checkResult,
  645. [=](Ui::PreparedList list) { addFiles(std::move(list)); },
  646. premium,
  647. show);
  648. };
  649. FileDialog::GetOpenPaths(
  650. this,
  651. tr::lng_choose_file(tr::now),
  652. FileDialog::AllOrImagesFilter(),
  653. crl::guard(this, callback));
  654. }
  655. void SendFilesBox::refreshMessagesCount() {
  656. const auto way = _sendWay.current();
  657. const auto withCaption = _list.canAddCaption(
  658. way.groupFiles() && way.sendImagesAsPhotos(),
  659. way.sendImagesAsPhotos());
  660. const auto withComment = !withCaption
  661. && _caption
  662. && !_caption->isHidden()
  663. && !_caption->getTextWithTags().text.isEmpty();
  664. _messagesCount = _list.files.size() + (withComment ? 1 : 0);
  665. }
  666. void SendFilesBox::refreshButtons() {
  667. clearButtons();
  668. _send = addButton(
  669. (_sendType == Api::SendType::Normal
  670. ? tr::lng_send_button()
  671. : tr::lng_create_group_next()),
  672. [=] { send({}); });
  673. refreshMessagesCount();
  674. const auto perMessage = _captionToPeer
  675. ? _captionToPeer->starsPerMessageChecked()
  676. : 0;
  677. if (perMessage > 0) {
  678. _send->setText(PaidSendButtonText(_messagesCount.value(
  679. ) | rpl::map(rpl::mappers::_1 * perMessage)));
  680. }
  681. if (_sendType == Api::SendType::Normal) {
  682. SendMenu::SetupMenuAndShortcuts(
  683. _send,
  684. _show,
  685. _sendMenuDetails,
  686. _sendMenuCallback);
  687. }
  688. addButton(tr::lng_cancel(), [=] { closeBox(); });
  689. _addFile = addLeftButton(
  690. tr::lng_stickers_featured_add(),
  691. base::fn_delayed(st::historyAttach.ripple.hideDuration, this, [=] {
  692. openDialogToAddFileToAlbum();
  693. }));
  694. addMenuButton();
  695. }
  696. bool SendFilesBox::hasSendMenu(const MenuDetails &details) const {
  697. return (details.type != SendMenu::Type::Disabled)
  698. || (details.spoiler != SendMenu::SpoilerState::None)
  699. || (details.caption != SendMenu::CaptionState::None);
  700. }
  701. bool SendFilesBox::hasSpoilerMenu() const {
  702. return !hasPrice()
  703. && _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos());
  704. }
  705. bool SendFilesBox::canChangePrice() const {
  706. const auto way = _sendWay.current();
  707. const auto broadcast = _captionToPeer
  708. ? _captionToPeer->asBroadcast()
  709. : nullptr;
  710. return broadcast
  711. && broadcast->canPostPaidMedia()
  712. && _list.canChangePrice(
  713. way.groupFiles() && way.sendImagesAsPhotos(),
  714. way.sendImagesAsPhotos());
  715. }
  716. void SendFilesBox::applyBlockChanges() {
  717. for (auto &block : _blocks) {
  718. block.applyChanges();
  719. }
  720. }
  721. bool SendFilesBox::allWithSpoilers() {
  722. applyBlockChanges();
  723. return ranges::all_of(_list.files, &Ui::PreparedFile::spoiler);
  724. }
  725. void SendFilesBox::toggleSpoilers(bool enabled) {
  726. for (auto &file : _list.files) {
  727. file.spoiler = enabled;
  728. }
  729. for (auto &block : _blocks) {
  730. block.toggleSpoilers(enabled);
  731. }
  732. }
  733. void SendFilesBox::changePrice() {
  734. const auto weak = Ui::MakeWeak(this);
  735. const auto session = &_show->session();
  736. const auto now = _price.current();
  737. _show->show(Box(EditPriceBox, session, now, [=](uint64 price) {
  738. if (weak && price != now) {
  739. _price = price;
  740. refreshPriceTag();
  741. }
  742. }));
  743. }
  744. bool SendFilesBox::hasPrice() const {
  745. return canChangePrice() && _price.current() > 0;
  746. }
  747. void SendFilesBox::refreshPriceTag() {
  748. const auto resetSpoilers = hasPrice() || _priceTag;
  749. if (resetSpoilers) {
  750. for (auto &file : _list.files) {
  751. file.spoiler = false;
  752. }
  753. for (auto &block : _blocks) {
  754. block.toggleSpoilers(hasPrice());
  755. }
  756. }
  757. if (!hasPrice()) {
  758. _priceTag = nullptr;
  759. _priceTagBg = QImage();
  760. } else if (!_priceTag) {
  761. _priceTag = std::make_unique<Ui::RpWidget>(_inner.data());
  762. const auto raw = _priceTag.get();
  763. raw->show();
  764. raw->paintRequest() | rpl::start_with_next([=] {
  765. if (_priceTagBg.isNull()) {
  766. _priceTagBg = preparePriceTagBg(raw->size());
  767. }
  768. QPainter(raw).drawImage(0, 0, _priceTagBg);
  769. }, raw->lifetime());
  770. const auto session = &_show->session();
  771. auto price = _price.value() | rpl::map([=](uint64 amount) {
  772. auto result = Ui::Text::Colorized(Ui::CreditsEmoji(session));
  773. result.append(Lang::FormatCountDecimal(amount));
  774. return result;
  775. });
  776. auto text = tr::lng_paid_price(
  777. lt_price,
  778. std::move(price),
  779. Ui::Text::WithEntities);
  780. const auto label = Ui::CreateChild<Ui::FlatLabel>(
  781. raw,
  782. QString(),
  783. st::paidTagLabel);
  784. std::move(text) | rpl::start_with_next([=](TextWithEntities &&text) {
  785. label->setMarkedText(text, Core::TextContext({
  786. .session = session,
  787. }));
  788. }, label->lifetime());
  789. label->show();
  790. label->sizeValue() | rpl::start_with_next([=](QSize size) {
  791. const auto inner = QRect(QPoint(), size);
  792. const auto rect = inner.marginsAdded(st::paidTagPadding);
  793. raw->resize(rect.size());
  794. label->move(-rect.topLeft());
  795. }, label->lifetime());
  796. _inner->sizeValue() | rpl::start_with_next([=](QSize size) {
  797. raw->move(
  798. (size.width() - raw->width()) / 2,
  799. (size.height() - raw->height()) / 2);
  800. }, raw->lifetime());
  801. } else {
  802. _priceTag->raise();
  803. _priceTag->update();
  804. _priceTagBg = QImage();
  805. }
  806. }
  807. QImage SendFilesBox::preparePriceTagBg(QSize size) const {
  808. const auto ratio = style::DevicePixelRatio();
  809. const auto outer = _blocks.empty()
  810. ? size
  811. : _inner->widgetAt(0)->geometry().size();
  812. auto bg = _blocks.empty()
  813. ? QImage()
  814. : _blocks.front().generatePriceTagBackground();
  815. if (bg.isNull()) {
  816. bg = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied);
  817. bg.fill(Qt::black);
  818. }
  819. auto result = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied);
  820. result.setDevicePixelRatio(ratio);
  821. result.fill(Qt::black);
  822. auto p = QPainter(&result);
  823. auto hq = PainterHighQualityEnabler(p);
  824. p.drawImage(
  825. QRect(
  826. (size.width() - outer.width()) / 2,
  827. (size.height() - outer.height()) / 2,
  828. outer.width(),
  829. outer.height()),
  830. bg);
  831. p.fillRect(QRect(QPoint(), size), st::msgDateImgBg);
  832. p.end();
  833. const auto radius = std::min(size.width(), size.height()) / 2;
  834. return Images::Round(std::move(result), Images::CornersMask(radius));
  835. }
  836. void SendFilesBox::addMenuButton() {
  837. const auto details = _sendMenuDetails();
  838. if (!hasSendMenu(details)) {
  839. return;
  840. }
  841. const auto top = addTopButton(_st.files.menu);
  842. top->setClickedCallback([=] {
  843. const auto &tabbed = _st.tabbed;
  844. _menu = base::make_unique_q<Ui::PopupMenu>(top, tabbed.menu);
  845. const auto position = QCursor::pos();
  846. SendMenu::FillSendMenu(
  847. _menu.get(),
  848. _show,
  849. _sendMenuDetails(),
  850. _sendMenuCallback,
  851. &_st.tabbed.icons,
  852. position);
  853. _menu->popup(position);
  854. return true;
  855. });
  856. }
  857. void SendFilesBox::initSendWay() {
  858. _sendWay = [&] {
  859. auto result = Core::App().settings().sendFilesWay();
  860. result.setHasCompressedStickers(_list.hasSticker());
  861. if ((_limits & SendFilesAllow::OnlyOne)
  862. && (_list.files.size() > 1)) {
  863. result.setGroupFiles(true);
  864. }
  865. if (_list.overrideSendImagesAsPhotos == false) {
  866. if (!(_limits & SendFilesAllow::OnlyOne)
  867. || !_list.hasSticker()) {
  868. result.setSendImagesAsPhotos(false);
  869. }
  870. return result;
  871. } else if (_list.overrideSendImagesAsPhotos == true) {
  872. result.setSendImagesAsPhotos(true);
  873. const auto silent = true;
  874. if (!checkWithWay(result, silent)) {
  875. result.setSendImagesAsPhotos(false);
  876. }
  877. return result;
  878. }
  879. const auto silent = true;
  880. if (!checkWithWay(result, silent)) {
  881. result.setSendImagesAsPhotos(!result.sendImagesAsPhotos());
  882. }
  883. return result;
  884. }();
  885. _sendWay.changes(
  886. ) | rpl::start_with_next([=](SendFilesWay value) {
  887. const auto hidden = [&] {
  888. return !_caption || _caption->isHidden();
  889. };
  890. const auto was = hidden();
  891. updateCaptionPlaceholder();
  892. updateEmojiPanelGeometry();
  893. for (auto &block : _blocks) {
  894. block.setSendWay(value);
  895. }
  896. refreshButtons();
  897. refreshPriceTag();
  898. if (was != hidden()) {
  899. updateBoxSize();
  900. updateControlsGeometry();
  901. }
  902. setInnerFocus();
  903. }, lifetime());
  904. }
  905. void SendFilesBox::updateCaptionPlaceholder() {
  906. if (!_caption) {
  907. return;
  908. }
  909. const auto way = _sendWay.current();
  910. if (!_list.canAddCaption(
  911. way.groupFiles() && way.sendImagesAsPhotos(),
  912. way.sendImagesAsPhotos())
  913. && ((_limits & SendFilesAllow::OnlyOne)
  914. || !(_limits & SendFilesAllow::Texts))) {
  915. _caption->hide();
  916. if (_emojiToggle) {
  917. _emojiToggle->hide();
  918. }
  919. } else {
  920. _caption->setPlaceholder(FieldPlaceholder(_list, way));
  921. _caption->show();
  922. if (_emojiToggle) {
  923. _emojiToggle->show();
  924. }
  925. }
  926. }
  927. void SendFilesBox::preparePreview() {
  928. generatePreviewFrom(0);
  929. }
  930. void SendFilesBox::generatePreviewFrom(int fromBlock) {
  931. Expects(fromBlock <= _blocks.size());
  932. using Type = Ui::PreparedFile::Type;
  933. _blocks.erase(_blocks.begin() + fromBlock, _blocks.end());
  934. const auto fromItem = _blocks.empty() ? 0 : _blocks.back().tillIndex();
  935. Assert(fromItem <= _list.files.size());
  936. auto albumStart = -1;
  937. for (auto i = fromItem, till = int(_list.files.size()); i != till; ++i) {
  938. const auto type = _list.files[i].type;
  939. if (albumStart >= 0) {
  940. const auto albumCount = (i - albumStart);
  941. if ((type == Type::File)
  942. || (type == Type::None)
  943. || (type == Type::Music)
  944. || (albumCount == Ui::MaxAlbumItems())) {
  945. pushBlock(std::exchange(albumStart, -1), i);
  946. } else {
  947. continue;
  948. }
  949. }
  950. if (type != Type::File
  951. && type != Type::Music
  952. && type != Type::None) {
  953. if (albumStart < 0) {
  954. albumStart = i;
  955. }
  956. continue;
  957. }
  958. pushBlock(i, i + 1);
  959. }
  960. if (albumStart >= 0) {
  961. pushBlock(albumStart, _list.files.size());
  962. }
  963. }
  964. void SendFilesBox::pushBlock(int from, int till) {
  965. const auto gifPaused = [show = _show] {
  966. return show->paused(Window::GifPauseReason::Layer);
  967. };
  968. _blocks.emplace_back(
  969. _inner.data(),
  970. _st,
  971. &_list.files,
  972. from,
  973. till,
  974. gifPaused,
  975. _sendWay.current(),
  976. [=](const Ui::PreparedFile &file, Ui::AttachActionType type) {
  977. return (type == Ui::AttachActionType::ToggleSpoiler)
  978. ? !hasPrice()
  979. : (type == Ui::AttachActionType::EditCover)
  980. ? (file.isVideoFile()
  981. && _captionToPeer
  982. && (_captionToPeer->isBroadcast()
  983. || _captionToPeer->isSelf()))
  984. : (file.videoCover != nullptr);
  985. });
  986. auto &block = _blocks.back();
  987. const auto widget = _inner->add(
  988. block.takeWidget(),
  989. QMargins(0, _inner->count() ? st::sendMediaRowSkip : 0, 0, 0));
  990. block.itemDeleteRequest(
  991. ) | rpl::filter([=] {
  992. return !_removingIndex;
  993. }) | rpl::start_with_next([=](int index) {
  994. applyBlockChanges();
  995. _removingIndex = index;
  996. crl::on_main(this, [=] {
  997. const auto index = base::take(_removingIndex).value_or(-1);
  998. if (index < 0 || index >= _list.files.size()) {
  999. return;
  1000. }
  1001. // Just close the box if it is the only one.
  1002. if (_list.files.size() == 1) {
  1003. closeBox();
  1004. return;
  1005. }
  1006. refreshAllAfterChanges(index, [&] {
  1007. _list.files.erase(_list.files.begin() + index);
  1008. });
  1009. });
  1010. }, widget->lifetime());
  1011. const auto show = uiShow();
  1012. block.itemReplaceRequest(
  1013. ) | rpl::start_with_next([=](int index) {
  1014. applyBlockChanges();
  1015. const auto replace = [=](Ui::PreparedList list) {
  1016. if (list.files.empty()) {
  1017. return;
  1018. }
  1019. refreshAllAfterChanges(from, [&] {
  1020. _list.files[index] = std::move(list.files.front());
  1021. });
  1022. };
  1023. const auto checkSlowmode = [=](const Ui::PreparedList &list) {
  1024. if (list.files.empty() || !(_limits & SendFilesAllow::OnlyOne)) {
  1025. return true;
  1026. }
  1027. auto removing = std::move(_list.files[index]);
  1028. std::swap(_list.files[index], _list.files.back());
  1029. _list.files.pop_back();
  1030. const auto result = _list.canBeSentInSlowmodeWith(list);
  1031. _list.files.push_back(std::move(removing));
  1032. std::swap(_list.files[index], _list.files.back());
  1033. if (!result) {
  1034. show->showToast(tr::lng_slowmode_no_many(tr::now));
  1035. return false;
  1036. }
  1037. return true;
  1038. };
  1039. const auto checkRights = [=](const Ui::PreparedList &list) {
  1040. if (list.files.empty()) {
  1041. return true;
  1042. }
  1043. auto removing = std::move(_list.files[index]);
  1044. std::swap(_list.files[index], _list.files.back());
  1045. _list.files.pop_back();
  1046. auto way = _sendWay.current();
  1047. const auto has = _list.hasSticker()
  1048. || list.files.front().isSticker();
  1049. way.setHasCompressedStickers(has);
  1050. if (_limits & SendFilesAllow::OnlyOne) {
  1051. way.setGroupFiles(true);
  1052. }
  1053. const auto silent = true;
  1054. if (!checkWith(list, way, silent)
  1055. && (!(_limits & SendFilesAllow::OnlyOne) || !has)) {
  1056. way.setSendImagesAsPhotos(!way.sendImagesAsPhotos());
  1057. }
  1058. const auto result = checkWith(list, way);
  1059. _list.files.push_back(std::move(removing));
  1060. std::swap(_list.files[index], _list.files.back());
  1061. if (!result) {
  1062. return false;
  1063. }
  1064. _sendWay = way;
  1065. return true;
  1066. };
  1067. const auto checkResult = [=](const Ui::PreparedList &list) {
  1068. return checkSlowmode(list) && checkRights(list);
  1069. };
  1070. const auto callback = [=](FileDialog::OpenResult &&result) {
  1071. const auto premium = _show->session().premium();
  1072. FileDialogCallback(
  1073. std::move(result),
  1074. checkResult,
  1075. replace,
  1076. premium,
  1077. show);
  1078. };
  1079. FileDialog::GetOpenPath(
  1080. this,
  1081. tr::lng_choose_file(tr::now),
  1082. FileDialog::AllOrImagesFilter(),
  1083. crl::guard(this, callback));
  1084. }, widget->lifetime());
  1085. const auto openedOnce = widget->lifetime().make_state<bool>(false);
  1086. block.itemModifyRequest(
  1087. ) | rpl::start_with_next([=, show = _show](int index) {
  1088. applyBlockChanges();
  1089. if (!(*openedOnce)) {
  1090. show->session().settings().incrementPhotoEditorHintShown();
  1091. show->session().saveSettings();
  1092. }
  1093. *openedOnce = true;
  1094. Editor::OpenWithPreparedFile(
  1095. this,
  1096. show,
  1097. &_list.files[index],
  1098. st::sendMediaPreviewSize,
  1099. [=](bool ok) { if (ok) refreshAllAfterChanges(from); });
  1100. }, widget->lifetime());
  1101. block.itemEditCoverRequest(
  1102. ) | rpl::start_with_next([=, show = _show](int index) {
  1103. applyBlockChanges();
  1104. const auto replace = [=](Ui::PreparedList list) {
  1105. if (list.files.empty()) {
  1106. return;
  1107. }
  1108. auto &entry = _list.files[index];
  1109. const auto video = entry.information
  1110. ? std::get_if<Ui::PreparedFileInformation::Video>(
  1111. &entry.information->media)
  1112. : nullptr;
  1113. if (!video) {
  1114. return;
  1115. }
  1116. auto old = std::shared_ptr<Ui::PreparedFile>(
  1117. std::move(entry.videoCover));
  1118. entry.videoCover = std::make_unique<Ui::PreparedFile>(
  1119. std::move(list.files.front()));
  1120. Editor::OpenWithPreparedFile(
  1121. this,
  1122. show,
  1123. entry.videoCover.get(),
  1124. st::sendMediaPreviewSize,
  1125. crl::guard(this, [=](bool ok) {
  1126. if (!ok) {
  1127. _list.files[index].videoCover = old
  1128. ? std::make_unique<Ui::PreparedFile>(
  1129. std::move(*old))
  1130. : nullptr;
  1131. }
  1132. refreshAllAfterChanges(from);
  1133. }),
  1134. video->thumbnail.size());
  1135. };
  1136. const auto checkResult = [=](const Ui::PreparedList &list) {
  1137. if (list.files.empty()) {
  1138. return true;
  1139. }
  1140. if (list.files.front().type != Ui::PreparedFile::Type::Photo) {
  1141. show->showToast(tr::lng_choose_cover_bad(tr::now));
  1142. return false;
  1143. }
  1144. return true;
  1145. };
  1146. const auto callback = [=](FileDialog::OpenResult &&result) {
  1147. const auto premium = _show->session().premium();
  1148. FileDialogCallback(
  1149. std::move(result),
  1150. checkResult,
  1151. replace,
  1152. premium,
  1153. show);
  1154. };
  1155. FileDialog::GetOpenPath(
  1156. this,
  1157. tr::lng_choose_cover(tr::now),
  1158. FileDialog::ImagesFilter(),
  1159. crl::guard(this, callback));
  1160. }, widget->lifetime());
  1161. block.itemClearCoverRequest(
  1162. ) | rpl::start_with_next([=](int index) {
  1163. applyBlockChanges();
  1164. refreshAllAfterChanges(from, [&] {
  1165. auto &entry = _list.files[index];
  1166. entry.videoCover = nullptr;
  1167. });
  1168. }, widget->lifetime());
  1169. block.orderUpdated() | rpl::start_with_next([=]{
  1170. if (_priceTag) {
  1171. _priceTagBg = QImage();
  1172. _priceTag->update();
  1173. }
  1174. }, widget->lifetime());
  1175. }
  1176. void SendFilesBox::refreshControls(bool initial) {
  1177. refreshButtons();
  1178. refreshPriceTag();
  1179. refreshTitleText();
  1180. updateSendWayControls();
  1181. updateCaptionPlaceholder();
  1182. }
  1183. void SendFilesBox::setupSendWayControls() {
  1184. const auto groupFilesFirst = _sendWay.current().groupFiles();
  1185. const auto asPhotosFirst = _sendWay.current().sendImagesAsPhotos();
  1186. _groupFiles.create(
  1187. this,
  1188. tr::lng_send_grouped(tr::now),
  1189. groupFilesFirst,
  1190. _st.files.checkbox,
  1191. _st.files.check);
  1192. _sendImagesAsPhotos.create(
  1193. this,
  1194. tr::lng_send_compressed(tr::now),
  1195. _sendWay.current().sendImagesAsPhotos(),
  1196. _st.files.checkbox,
  1197. _st.files.check);
  1198. _sendWay.changes(
  1199. ) | rpl::start_with_next([=](SendFilesWay value) {
  1200. _groupFiles->setChecked(value.groupFiles());
  1201. _sendImagesAsPhotos->setChecked(value.sendImagesAsPhotos());
  1202. }, lifetime());
  1203. _groupFiles->checkedChanges(
  1204. ) | rpl::start_with_next([=](bool checked) {
  1205. auto sendWay = _sendWay.current();
  1206. if (sendWay.groupFiles() == checked) {
  1207. return;
  1208. }
  1209. sendWay.setGroupFiles(checked);
  1210. if (checkWithWay(sendWay)) {
  1211. _sendWay = sendWay;
  1212. } else {
  1213. Ui::PostponeCall(_groupFiles.data(), [=] {
  1214. _groupFiles->setChecked(!checked);
  1215. });
  1216. }
  1217. }, lifetime());
  1218. _sendImagesAsPhotos->checkedChanges(
  1219. ) | rpl::start_with_next([=](bool checked) {
  1220. auto sendWay = _sendWay.current();
  1221. if (sendWay.sendImagesAsPhotos() == checked) {
  1222. return;
  1223. }
  1224. sendWay.setSendImagesAsPhotos(checked);
  1225. if (checkWithWay(sendWay)) {
  1226. _sendWay = sendWay;
  1227. } else {
  1228. Ui::PostponeCall(_sendImagesAsPhotos.data(), [=] {
  1229. _sendImagesAsPhotos->setChecked(!checked);
  1230. });
  1231. }
  1232. }, lifetime());
  1233. _wayRemember.create(
  1234. this,
  1235. tr::lng_remember(tr::now),
  1236. false,
  1237. _st.files.checkbox,
  1238. _st.files.check);
  1239. _wayRemember->hide();
  1240. rpl::combine(
  1241. _groupFiles->checkedValue(),
  1242. _sendImagesAsPhotos->checkedValue()
  1243. ) | rpl::start_with_next([=](bool groupFiles, bool asPhoto) {
  1244. _wayRemember->setVisible(
  1245. (groupFiles != groupFilesFirst) || (asPhoto != asPhotosFirst));
  1246. captionResized();
  1247. }, lifetime());
  1248. _hintLabel.create(
  1249. this,
  1250. tr::lng_edit_photo_editor_hint(tr::now),
  1251. st::editMediaHintLabel);
  1252. }
  1253. bool SendFilesBox::checkWithWay(Ui::SendFilesWay way, bool silent) const {
  1254. return checkWith({}, way, silent);
  1255. }
  1256. bool SendFilesBox::checkWith(
  1257. const Ui::PreparedList &added,
  1258. Ui::SendFilesWay way,
  1259. bool silent) const {
  1260. if (!_check) {
  1261. return true;
  1262. }
  1263. const auto compress = way.sendImagesAsPhotos();
  1264. auto &already = _list.files;
  1265. for (const auto &file : ranges::views::concat(already, added.files)) {
  1266. if (!_check(file, compress, silent)) {
  1267. return false;
  1268. }
  1269. }
  1270. return true;
  1271. }
  1272. void SendFilesBox::updateSendWayControls() {
  1273. const auto onlyOne = (_limits & SendFilesAllow::OnlyOne);
  1274. _groupFiles->setVisible(_list.hasGroupOption(onlyOne));
  1275. _sendImagesAsPhotos->setVisible(
  1276. _list.hasSendImagesAsPhotosOption(onlyOne));
  1277. _sendImagesAsPhotos->setText((_list.files.size() > 1)
  1278. ? tr::lng_send_compressed(tr::now)
  1279. : tr::lng_send_compressed_one(tr::now));
  1280. _hintLabel->setVisible(
  1281. _show->session().settings().photoEditorHintShown()
  1282. ? _list.canHaveEditorHintLabel()
  1283. : false);
  1284. }
  1285. void SendFilesBox::setupCaption() {
  1286. const auto allow = [=](not_null<DocumentData*> emoji) {
  1287. return _captionToPeer
  1288. ? Data::AllowEmojiWithoutPremium(_captionToPeer, emoji)
  1289. : (_limits & SendFilesAllow::EmojiWithoutPremium);
  1290. };
  1291. const auto show = _show;
  1292. InitMessageFieldHandlers({
  1293. .session = &show->session(),
  1294. .show = show,
  1295. .field = _caption.data(),
  1296. .customEmojiPaused = [=] {
  1297. return show->paused(Window::GifPauseReason::Layer);
  1298. },
  1299. .allowPremiumEmoji = allow,
  1300. .fieldStyle = &_st.files.caption,
  1301. });
  1302. setupCaptionAutocomplete();
  1303. Ui::Emoji::SuggestionsController::Init(
  1304. getDelegate()->outerContainer(),
  1305. _caption,
  1306. &_show->session(),
  1307. {
  1308. .suggestCustomEmoji = true,
  1309. .allowCustomWithoutPremium = allow,
  1310. .st = &_st.suggestions,
  1311. });
  1312. if (!_prefilledCaptionText.text.isEmpty()) {
  1313. _caption->setTextWithTags(
  1314. _prefilledCaptionText,
  1315. Ui::InputField::HistoryAction::Clear);
  1316. auto cursor = _caption->textCursor();
  1317. cursor.movePosition(QTextCursor::End);
  1318. _caption->setTextCursor(cursor);
  1319. }
  1320. _caption->setSubmitSettings(
  1321. Core::App().settings().sendSubmitWay());
  1322. _caption->setMaxLength(kMaxMessageLength);
  1323. _caption->heightChanges(
  1324. ) | rpl::start_with_next([=] {
  1325. captionResized();
  1326. }, _caption->lifetime());
  1327. _caption->submits(
  1328. ) | rpl::start_with_next([=](Qt::KeyboardModifiers modifiers) {
  1329. const auto ctrlShiftEnter = modifiers.testFlag(Qt::ShiftModifier)
  1330. && (modifiers.testFlag(Qt::ControlModifier)
  1331. || modifiers.testFlag(Qt::MetaModifier));
  1332. send({}, ctrlShiftEnter);
  1333. }, _caption->lifetime());
  1334. _caption->cancelled(
  1335. ) | rpl::start_with_next([=] {
  1336. closeBox();
  1337. }, _caption->lifetime());
  1338. _caption->setMimeDataHook([=](
  1339. not_null<const QMimeData*> data,
  1340. Ui::InputField::MimeAction action) {
  1341. if (action == Ui::InputField::MimeAction::Check) {
  1342. return CanAddFiles(data);
  1343. } else if (action == Ui::InputField::MimeAction::Insert) {
  1344. return addFiles(data);
  1345. }
  1346. Unexpected("action in MimeData hook.");
  1347. });
  1348. updateCaptionPlaceholder();
  1349. setupEmojiPanel();
  1350. rpl::single(rpl::empty_value()) | rpl::then(
  1351. _caption->changes()
  1352. ) | rpl::start_with_next([=] {
  1353. checkCharsLimitation();
  1354. refreshMessagesCount();
  1355. }, _caption->lifetime());
  1356. }
  1357. void SendFilesBox::setupCaptionAutocomplete() {
  1358. if (!_captionToPeer || !_caption) {
  1359. return;
  1360. }
  1361. const auto parent = getDelegate()->outerContainer();
  1362. ChatHelpers::InitFieldAutocomplete(_autocomplete, {
  1363. .parent = parent,
  1364. .show = _show,
  1365. .field = _caption.data(),
  1366. .peer = _captionToPeer,
  1367. .features = [=] {
  1368. auto result = ChatHelpers::ComposeFeatures();
  1369. result.autocompleteCommands = false;
  1370. result.suggestStickersByEmoji = false;
  1371. return result;
  1372. },
  1373. .sendMenuDetails = _sendMenuDetails,
  1374. });
  1375. const auto raw = _autocomplete.get();
  1376. const auto scheduled = std::make_shared<bool>();
  1377. const auto recountPostponed = [=] {
  1378. if (*scheduled) {
  1379. return;
  1380. }
  1381. *scheduled = true;
  1382. Ui::PostponeCall(raw, [=] {
  1383. *scheduled = false;
  1384. auto field = Ui::MapFrom(parent, this, _caption->geometry());
  1385. _autocomplete->setBoundings(QRect(
  1386. field.x() - _caption->x(),
  1387. st::defaultBox.margin.top(),
  1388. width(),
  1389. (field.y()
  1390. + _st.files.caption.textMargins.top()
  1391. + _st.files.caption.placeholderShift
  1392. + _st.files.caption.placeholderFont->height
  1393. - st::defaultBox.margin.top())));
  1394. });
  1395. };
  1396. for (auto w = (QWidget*)_caption.data(); w; w = w->parentWidget()) {
  1397. base::install_event_filter(raw, w, [=](not_null<QEvent*> e) {
  1398. if (e->type() == QEvent::Move || e->type() == QEvent::Resize) {
  1399. recountPostponed();
  1400. }
  1401. return base::EventFilterResult::Continue;
  1402. });
  1403. if (w == parent) {
  1404. break;
  1405. }
  1406. }
  1407. }
  1408. void SendFilesBox::checkCharsLimitation() {
  1409. const auto limits = Data::PremiumLimits(&_show->session());
  1410. const auto caption = (_caption && !_caption->isHidden())
  1411. ? _caption->getTextWithAppliedMarkdown()
  1412. : TextWithTags();
  1413. const auto remove = caption.text.size() - limits.captionLengthCurrent();
  1414. if ((remove > 0) && _emojiToggle) {
  1415. if (!_charsLimitation) {
  1416. _charsLimitation = base::make_unique_q<CharactersLimitLabel>(
  1417. this,
  1418. _emojiToggle.data(),
  1419. style::al_top);
  1420. _charsLimitation->show();
  1421. Data::AmPremiumValue(
  1422. &_show->session()
  1423. ) | rpl::start_with_next([=] {
  1424. checkCharsLimitation();
  1425. }, _charsLimitation->lifetime());
  1426. }
  1427. _charsLimitation->setLeft(remove);
  1428. } else {
  1429. if (_charsLimitation) {
  1430. _charsLimitation = nullptr;
  1431. }
  1432. }
  1433. }
  1434. void SendFilesBox::setupEmojiPanel() {
  1435. Expects(_caption != nullptr);
  1436. const auto container = getDelegate()->outerContainer();
  1437. using Selector = ChatHelpers::TabbedSelector;
  1438. _emojiPanel = base::make_unique_q<ChatHelpers::TabbedPanel>(
  1439. container,
  1440. ChatHelpers::TabbedPanelDescriptor{
  1441. .ownedSelector = object_ptr<Selector>(
  1442. nullptr,
  1443. ChatHelpers::TabbedSelectorDescriptor{
  1444. .show = _show,
  1445. .st = _st.tabbed,
  1446. .level = Window::GifPauseReason::Layer,
  1447. .mode = ChatHelpers::TabbedSelector::Mode::EmojiOnly,
  1448. .features = {
  1449. .stickersSettings = false,
  1450. .openStickerSets = false,
  1451. },
  1452. }),
  1453. });
  1454. _emojiPanel->setDesiredHeightValues(
  1455. 1.,
  1456. st::emojiPanMinHeight / 2,
  1457. st::emojiPanMinHeight);
  1458. _emojiPanel->hide();
  1459. _emojiPanel->selector()->setCurrentPeer(_captionToPeer);
  1460. _emojiPanel->selector()->setAllowEmojiWithoutPremium(
  1461. _limits & SendFilesAllow::EmojiWithoutPremium);
  1462. _emojiPanel->selector()->emojiChosen(
  1463. ) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
  1464. Ui::InsertEmojiAtCursor(_caption->textCursor(), data.emoji);
  1465. }, lifetime());
  1466. _emojiPanel->selector()->customEmojiChosen(
  1467. ) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
  1468. const auto info = data.document->sticker();
  1469. if (info
  1470. && info->setType == Data::StickersType::Emoji
  1471. && !_show->session().premium()
  1472. && !(_captionToPeer
  1473. ? Data::AllowEmojiWithoutPremium(
  1474. _captionToPeer,
  1475. data.document)
  1476. : (_limits & SendFilesAllow::EmojiWithoutPremium))) {
  1477. ShowPremiumPreviewBox(_show, PremiumFeature::AnimatedEmoji);
  1478. } else {
  1479. Data::InsertCustomEmoji(_caption.data(), data.document);
  1480. }
  1481. }, lifetime());
  1482. const auto filterCallback = [=](not_null<QEvent*> event) {
  1483. emojiFilterForGeometry(event);
  1484. return base::EventFilterResult::Continue;
  1485. };
  1486. _emojiFilter.reset(base::install_event_filter(container, filterCallback));
  1487. _emojiToggle.create(this, _st.files.emoji);
  1488. _emojiToggle->setVisible(!_caption->isHidden());
  1489. _emojiToggle->installEventFilter(_emojiPanel);
  1490. _emojiToggle->addClickHandler([=] {
  1491. _emojiPanel->toggleAnimated();
  1492. });
  1493. }
  1494. void SendFilesBox::emojiFilterForGeometry(not_null<QEvent*> event) {
  1495. const auto type = event->type();
  1496. if (type == QEvent::Move || type == QEvent::Resize) {
  1497. // updateEmojiPanelGeometry uses not only container geometry, but
  1498. // also container children geometries that will be updated later.
  1499. crl::on_main(this, [=] { updateEmojiPanelGeometry(); });
  1500. }
  1501. }
  1502. void SendFilesBox::updateEmojiPanelGeometry() {
  1503. const auto parent = _emojiPanel->parentWidget();
  1504. const auto global = _emojiToggle->mapToGlobal({ 0, 0 });
  1505. const auto local = parent->mapFromGlobal(global);
  1506. _emojiPanel->moveBottomRight(
  1507. local.y(),
  1508. local.x() + _emojiToggle->width() * 3);
  1509. }
  1510. void SendFilesBox::captionResized() {
  1511. updateBoxSize();
  1512. updateControlsGeometry();
  1513. updateEmojiPanelGeometry();
  1514. update();
  1515. }
  1516. bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
  1517. const auto premium = _show->session().premium();
  1518. auto list = [&] {
  1519. const auto urls = Core::ReadMimeUrls(data);
  1520. auto result = CanAddUrls(urls)
  1521. ? Storage::PrepareMediaList(
  1522. urls,
  1523. st::sendMediaPreviewSize,
  1524. premium)
  1525. : Ui::PreparedList(
  1526. Ui::PreparedList::Error::EmptyFile,
  1527. QString());
  1528. if (result.error == Ui::PreparedList::Error::None) {
  1529. return result;
  1530. } else if (auto read = Core::ReadMimeImage(data)) {
  1531. return Storage::PrepareMediaFromImage(
  1532. std::move(read.image),
  1533. std::move(read.content),
  1534. st::sendMediaPreviewSize);
  1535. }
  1536. return result;
  1537. }();
  1538. return addFiles(std::move(list));
  1539. }
  1540. bool SendFilesBox::addFiles(Ui::PreparedList list) {
  1541. if (list.error != Ui::PreparedList::Error::None) {
  1542. return false;
  1543. }
  1544. const auto count = int(_list.files.size());
  1545. _list.filesToProcess.insert(
  1546. _list.filesToProcess.end(),
  1547. std::make_move_iterator(list.files.begin()),
  1548. std::make_move_iterator(list.files.end()));
  1549. _list.filesToProcess.insert(
  1550. _list.filesToProcess.end(),
  1551. std::make_move_iterator(list.filesToProcess.begin()),
  1552. std::make_move_iterator(list.filesToProcess.end()));
  1553. enqueueNextPrepare();
  1554. if (_list.files.size() > count) {
  1555. refreshAllAfterChanges(count);
  1556. }
  1557. return true;
  1558. }
  1559. void SendFilesBox::addPreparedAsyncFile(Ui::PreparedFile &&file) {
  1560. Expects(file.information != nullptr);
  1561. _preparing = false;
  1562. const auto count = int(_list.files.size());
  1563. addFile(std::move(file));
  1564. enqueueNextPrepare();
  1565. if (_list.files.size() > count) {
  1566. refreshAllAfterChanges(count);
  1567. }
  1568. if (!_preparing && _whenReadySend) {
  1569. _whenReadySend();
  1570. }
  1571. }
  1572. void SendFilesBox::addFile(Ui::PreparedFile &&file) {
  1573. // canBeSentInSlowmode checks for non empty filesToProcess.
  1574. auto saved = base::take(_list.filesToProcess);
  1575. _list.files.push_back(std::move(file));
  1576. const auto lastOk = [&] {
  1577. auto way = _sendWay.current();
  1578. if (_limits & SendFilesAllow::OnlyOne) {
  1579. way.setGroupFiles(true);
  1580. if (!_list.canBeSentInSlowmode()) {
  1581. return false;
  1582. }
  1583. } else if (!checkWithWay(way)) {
  1584. return false;
  1585. }
  1586. _sendWay = way;
  1587. return true;
  1588. }();
  1589. if (!lastOk) {
  1590. _list.files.pop_back();
  1591. }
  1592. _list.filesToProcess = std::move(saved);
  1593. }
  1594. void SendFilesBox::refreshTitleText() {
  1595. using Type = Ui::PreparedFile::Type;
  1596. const auto count = int(_list.files.size());
  1597. if (count > 1) {
  1598. const auto imagesCount = ranges::count(
  1599. _list.files,
  1600. Type::Photo,
  1601. &Ui::PreparedFile::type);
  1602. _titleText = (imagesCount == count)
  1603. ? tr::lng_send_images_selected(tr::now, lt_count, count)
  1604. : tr::lng_send_files_selected(tr::now, lt_count, count);
  1605. } else {
  1606. const auto type = _list.files.empty()
  1607. ? Type::None
  1608. : _list.files.front().type;
  1609. _titleText = (type == Type::Photo)
  1610. ? tr::lng_send_image(tr::now)
  1611. : (type == Type::Video)
  1612. ? tr::lng_send_video(tr::now)
  1613. : tr::lng_send_file(tr::now);
  1614. }
  1615. _titleHeight = st::boxTitleHeight;
  1616. }
  1617. void SendFilesBox::updateBoxSize() {
  1618. auto footerHeight = 0;
  1619. if (_caption && !_caption->isHidden()) {
  1620. footerHeight += st::boxPhotoCaptionSkip + _caption->height();
  1621. }
  1622. const auto pairs = std::array<std::pair<RpWidget*, int>, 4>{ {
  1623. { _groupFiles.data(), st::boxPhotoCompressedSkip },
  1624. { _sendImagesAsPhotos.data(), st::boxPhotoCompressedSkip },
  1625. { _wayRemember.data(), st::boxPhotoCompressedSkip },
  1626. { _hintLabel.data(), st::editMediaLabelMargins.top() },
  1627. } };
  1628. for (const auto &pair : pairs) {
  1629. const auto pointer = pair.first;
  1630. if (pointer && !pointer->isHidden()) {
  1631. footerHeight += pair.second + pointer->heightNoMargins();
  1632. }
  1633. }
  1634. _footerHeight = footerHeight;
  1635. }
  1636. void SendFilesBox::keyPressEvent(QKeyEvent *e) {
  1637. if (e->matches(QKeySequence::Open)) {
  1638. openDialogToAddFileToAlbum();
  1639. } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
  1640. const auto modifiers = e->modifiers();
  1641. const auto ctrl = modifiers.testFlag(Qt::ControlModifier)
  1642. || modifiers.testFlag(Qt::MetaModifier);
  1643. const auto shift = modifiers.testFlag(Qt::ShiftModifier);
  1644. send({}, ctrl && shift);
  1645. } else {
  1646. BoxContent::keyPressEvent(e);
  1647. }
  1648. }
  1649. void SendFilesBox::paintEvent(QPaintEvent *e) {
  1650. BoxContent::paintEvent(e);
  1651. if (!_titleText.isEmpty()) {
  1652. Painter p(this);
  1653. p.setFont(st::boxTitleFont);
  1654. p.setPen(getDelegate()->style().title.textFg);
  1655. p.drawTextLeft(
  1656. st::boxPhotoTitlePosition.x(),
  1657. st::boxTitlePosition.y() - st::boxTopMargin,
  1658. width(),
  1659. _titleText);
  1660. }
  1661. }
  1662. void SendFilesBox::resizeEvent(QResizeEvent *e) {
  1663. BoxContent::resizeEvent(e);
  1664. updateControlsGeometry();
  1665. }
  1666. void SendFilesBox::updateControlsGeometry() {
  1667. auto bottom = height();
  1668. if (_caption && !_caption->isHidden()) {
  1669. _caption->resize(st::sendMediaPreviewSize, _caption->height());
  1670. _caption->moveToLeft(
  1671. st::boxPhotoPadding.left(),
  1672. bottom - _caption->height());
  1673. bottom -= st::boxPhotoCaptionSkip + _caption->height();
  1674. if (_emojiToggle) {
  1675. _emojiToggle->moveToLeft(
  1676. (st::boxPhotoPadding.left()
  1677. + st::sendMediaPreviewSize
  1678. - _emojiToggle->width()),
  1679. _caption->y() + st::boxAttachEmojiTop);
  1680. _emojiToggle->update();
  1681. }
  1682. }
  1683. const auto pairs = std::array<std::pair<RpWidget*, int>, 4>{ {
  1684. { _hintLabel.data(), st::editMediaLabelMargins.top() },
  1685. { _groupFiles.data(), st::boxPhotoCompressedSkip },
  1686. { _sendImagesAsPhotos.data(), st::boxPhotoCompressedSkip },
  1687. { _wayRemember.data(), st::boxPhotoCompressedSkip },
  1688. } };
  1689. for (const auto &pair : ranges::views::reverse(pairs)) {
  1690. const auto pointer = pair.first;
  1691. if (pointer && !pointer->isHidden()) {
  1692. pointer->moveToLeft(
  1693. st::boxPhotoPadding.left(),
  1694. bottom - pointer->heightNoMargins());
  1695. bottom -= pair.second + pointer->heightNoMargins();
  1696. }
  1697. }
  1698. _scroll->resize(width(), bottom - _titleHeight.current());
  1699. _scroll->move(0, _titleHeight.current());
  1700. }
  1701. void SendFilesBox::showFinished() {
  1702. if (const auto raw = _autocomplete.get()) {
  1703. InvokeQueued(raw, [=] {
  1704. raw->raise();
  1705. });
  1706. }
  1707. }
  1708. void SendFilesBox::setInnerFocus() {
  1709. if (_caption && !_caption->isHidden()) {
  1710. _caption->setFocusFast();
  1711. } else {
  1712. BoxContent::setInnerFocus();
  1713. }
  1714. }
  1715. void SendFilesBox::saveSendWaySettings() {
  1716. auto way = _sendWay.current();
  1717. auto oldWay = Core::App().settings().sendFilesWay();
  1718. if (_groupFiles->isHidden()) {
  1719. way.setGroupFiles(oldWay.groupFiles());
  1720. }
  1721. if (_list.overrideSendImagesAsPhotos == way.sendImagesAsPhotos()
  1722. || _sendImagesAsPhotos->isHidden()) {
  1723. way.setSendImagesAsPhotos(oldWay.sendImagesAsPhotos());
  1724. }
  1725. if (way != oldWay) {
  1726. Core::App().settings().setSendFilesWay(way);
  1727. Core::App().saveSettingsDelayed();
  1728. }
  1729. }
  1730. bool SendFilesBox::validateLength(const QString &text) const {
  1731. const auto session = &_show->session();
  1732. const auto limit = Data::PremiumLimits(session).captionLengthCurrent();
  1733. const auto remove = int(text.size()) - limit;
  1734. const auto way = _sendWay.current();
  1735. if (remove <= 0
  1736. || !_list.canAddCaption(
  1737. way.groupFiles() && way.sendImagesAsPhotos(),
  1738. way.sendImagesAsPhotos())) {
  1739. return true;
  1740. }
  1741. _show->showBox(
  1742. Box(CaptionLimitReachedBox, session, remove, &_st.premium));
  1743. return false;
  1744. }
  1745. void SendFilesBox::send(
  1746. Api::SendOptions options,
  1747. bool ctrlShiftEnter) {
  1748. if ((_sendType == Api::SendType::Scheduled
  1749. || _sendType == Api::SendType::ScheduledToUser)
  1750. && !options.scheduled) {
  1751. auto child = _sendMenuDetails();
  1752. child.spoiler = SendMenu::SpoilerState::None;
  1753. child.caption = SendMenu::CaptionState::None;
  1754. child.price = std::nullopt;
  1755. return SendMenu::DefaultCallback(_show, sendCallback())(
  1756. { .type = SendMenu::ActionType::Schedule },
  1757. child);
  1758. }
  1759. if (_preparing) {
  1760. _whenReadySend = [=] {
  1761. send(options, ctrlShiftEnter);
  1762. };
  1763. return;
  1764. }
  1765. if (_wayRemember && _wayRemember->checked()) {
  1766. saveSendWaySettings();
  1767. }
  1768. for (auto &item : _list.files) {
  1769. item.spoiler = false;
  1770. }
  1771. applyBlockChanges();
  1772. Storage::ApplyModifications(_list);
  1773. _confirmed = true;
  1774. if (_confirmedCallback) {
  1775. auto caption = (_caption && !_caption->isHidden())
  1776. ? _caption->getTextWithAppliedMarkdown()
  1777. : TextWithTags();
  1778. if (!validateLength(caption.text)) {
  1779. return;
  1780. }
  1781. options.invertCaption = _invertCaption;
  1782. options.price = hasPrice() ? _price.current() : 0;
  1783. if (options.price > 0) {
  1784. for (auto &file : _list.files) {
  1785. file.spoiler = false;
  1786. }
  1787. }
  1788. _confirmedCallback(
  1789. std::move(_list),
  1790. _sendWay.current(),
  1791. std::move(caption),
  1792. options,
  1793. ctrlShiftEnter);
  1794. }
  1795. closeBox();
  1796. }
  1797. Fn<void(Api::SendOptions)> SendFilesBox::sendCallback() {
  1798. return crl::guard(this, [=](Api::SendOptions options) {
  1799. send(options, false);
  1800. });
  1801. }
  1802. SendFilesBox::~SendFilesBox() = default;