export_view_settings.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953
  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 "export/view/export_view_settings.h"
  8. #include "export/output/export_output_abstract.h"
  9. #include "export/view/export_view_panel_controller.h"
  10. #include "lang/lang_keys.h"
  11. #include "ui/widgets/checkbox.h"
  12. #include "ui/widgets/buttons.h"
  13. #include "ui/widgets/labels.h"
  14. #include "ui/widgets/scroll_area.h"
  15. #include "ui/widgets/continuous_sliders.h"
  16. #include "ui/wrap/vertical_layout.h"
  17. #include "ui/wrap/padding_wrap.h"
  18. #include "ui/wrap/slide_wrap.h"
  19. #include "ui/wrap/fade_wrap.h"
  20. #include "ui/layers/generic_box.h"
  21. #include "ui/text/text_utilities.h"
  22. #include "ui/boxes/calendar_box.h"
  23. #include "ui/boxes/choose_time.h"
  24. #include "platform/platform_specific.h"
  25. #include "core/application.h"
  26. #include "core/file_utilities.h"
  27. #include "base/unixtime.h"
  28. #include "main/main_session.h"
  29. #include "styles/style_widgets.h"
  30. #include "styles/style_export.h"
  31. #include "styles/style_layers.h"
  32. namespace Export {
  33. namespace View {
  34. namespace {
  35. constexpr auto kMegabyte = int64(1024) * 1024;
  36. [[nodiscard]] PeerId ReadPeerId(
  37. not_null<Main::Session*> session,
  38. const MTPInputPeer &data) {
  39. return data.match([](const MTPDinputPeerUser &data) {
  40. return peerFromUser(data.vuser_id().v);
  41. }, [](const MTPDinputPeerUserFromMessage &data) {
  42. return peerFromUser(data.vuser_id().v);
  43. }, [](const MTPDinputPeerChat &data) {
  44. return peerFromChat(data.vchat_id().v);
  45. }, [](const MTPDinputPeerChannel &data) {
  46. return peerFromChannel(data.vchannel_id().v);
  47. }, [](const MTPDinputPeerChannelFromMessage &data) {
  48. return peerFromChannel(data.vchannel_id().v);
  49. }, [&](const MTPDinputPeerSelf &data) {
  50. return session->userPeerId();
  51. }, [](const MTPDinputPeerEmpty &data) {
  52. return PeerId(0);
  53. });
  54. }
  55. void ChooseFormatBox(
  56. not_null<Ui::GenericBox*> box,
  57. Output::Format format,
  58. Fn<void(Output::Format)> done) {
  59. using Format = Output::Format;
  60. const auto group = std::make_shared<Ui::RadioenumGroup<Format>>(format);
  61. const auto addFormatOption = [&](QString label, Format format) {
  62. box->addRow(
  63. object_ptr<Ui::Radioenum<Format>>(
  64. box,
  65. group,
  66. format,
  67. label,
  68. st::defaultBoxCheckbox),
  69. st::exportSettingPadding);
  70. };
  71. box->setTitle(tr::lng_export_option_choose_format());
  72. addFormatOption(tr::lng_export_option_html(tr::now), Format::Html);
  73. addFormatOption(tr::lng_export_option_json(tr::now), Format::Json);
  74. addFormatOption(
  75. tr::lng_export_option_html_and_json(tr::now),
  76. Format::HtmlAndJson);
  77. box->addButton(tr::lng_settings_save(), [=] { done(group->current()); });
  78. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  79. }
  80. } // namespace
  81. int64 SizeLimitByIndex(int index) {
  82. Expects(index >= 0 && index < kSizeValueCount);
  83. index += 1;
  84. const auto megabytes = [&] {
  85. if (index <= 10) {
  86. return index;
  87. } else if (index <= 30) {
  88. return 10 + (index - 10) * 2;
  89. } else if (index <= 40) {
  90. return 50 + (index - 30) * 5;
  91. } else if (index <= 60) {
  92. return 100 + (index - 40) * 10;
  93. } else if (index <= 70) {
  94. return 300 + (index - 60) * 20;
  95. } else if (index <= 80) {
  96. return 500 + (index - 70) * 50;
  97. } else if (index <= 90) {
  98. return 1000 + (index - 80) * 100;
  99. } else {
  100. return 2000 + (index - 90) * 200;
  101. }
  102. }();
  103. return megabytes * kMegabyte;
  104. }
  105. SettingsWidget::SettingsWidget(
  106. QWidget *parent,
  107. not_null<Main::Session*> session,
  108. Settings data)
  109. : RpWidget(parent)
  110. , _session(session)
  111. , _singlePeerId(ReadPeerId(session, data.singlePeer))
  112. , _internal_data(std::move(data)) {
  113. ResolveSettings(session, _internal_data);
  114. setupContent();
  115. }
  116. const Settings &SettingsWidget::readData() const {
  117. return _internal_data;
  118. }
  119. template <typename Callback>
  120. void SettingsWidget::changeData(Callback &&callback) {
  121. callback(_internal_data);
  122. _changes.fire_copy(_internal_data);
  123. }
  124. void SettingsWidget::setupContent() {
  125. const auto scroll = Ui::CreateChild<Ui::ScrollArea>(
  126. this,
  127. st::boxScroll);
  128. const auto wrap = scroll->setOwnedWidget(
  129. object_ptr<Ui::OverrideMargins>(
  130. scroll,
  131. object_ptr<Ui::VerticalLayout>(scroll)));
  132. const auto content = static_cast<Ui::VerticalLayout*>(wrap->entity());
  133. const auto buttons = setupButtons(scroll, wrap);
  134. setupOptions(content);
  135. setupPathAndFormat(content);
  136. sizeValue(
  137. ) | rpl::start_with_next([=](QSize size) {
  138. scroll->resize(size.width(), size.height() - buttons->height());
  139. wrap->resizeToWidth(size.width());
  140. content->resizeToWidth(size.width());
  141. }, lifetime());
  142. }
  143. void SettingsWidget::setupOptions(not_null<Ui::VerticalLayout*> container) {
  144. if (!_singlePeerId) {
  145. setupFullExportOptions(container);
  146. }
  147. setupMediaOptions(container);
  148. if (!_singlePeerId) {
  149. setupOtherOptions(container);
  150. }
  151. }
  152. void SettingsWidget::setupFullExportOptions(
  153. not_null<Ui::VerticalLayout*> container) {
  154. addOptionWithAbout(
  155. container,
  156. tr::lng_export_option_info(tr::now),
  157. Type::PersonalInfo | Type::Userpics,
  158. tr::lng_export_option_info_about(tr::now));
  159. addOptionWithAbout(
  160. container,
  161. tr::lng_export_option_contacts(tr::now),
  162. Type::Contacts,
  163. tr::lng_export_option_contacts_about(tr::now));
  164. addOptionWithAbout(
  165. container,
  166. tr::lng_export_option_stories(tr::now),
  167. Type::Stories,
  168. tr::lng_export_option_stories_about(tr::now));
  169. addHeader(container, tr::lng_export_header_chats(tr::now));
  170. addOption(
  171. container,
  172. tr::lng_export_option_personal_chats(tr::now),
  173. Type::PersonalChats);
  174. addOption(
  175. container,
  176. tr::lng_export_option_bot_chats(tr::now),
  177. Type::BotChats);
  178. addChatOption(
  179. container,
  180. tr::lng_export_option_private_groups(tr::now),
  181. Type::PrivateGroups);
  182. addChatOption(
  183. container,
  184. tr::lng_export_option_private_channels(tr::now),
  185. Type::PrivateChannels);
  186. addChatOption(
  187. container,
  188. tr::lng_export_option_public_groups(tr::now),
  189. Type::PublicGroups);
  190. addChatOption(
  191. container,
  192. tr::lng_export_option_public_channels(tr::now),
  193. Type::PublicChannels);
  194. }
  195. void SettingsWidget::setupMediaOptions(
  196. not_null<Ui::VerticalLayout*> container) {
  197. if (_singlePeerId != 0) {
  198. addMediaOptions(container);
  199. return;
  200. }
  201. const auto mediaWrap = container->add(
  202. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  203. container,
  204. object_ptr<Ui::VerticalLayout>(container)));
  205. const auto media = mediaWrap->entity();
  206. addHeader(media, tr::lng_export_header_media(tr::now));
  207. addMediaOptions(media);
  208. value() | rpl::map([](const Settings &data) {
  209. return data.types;
  210. }) | rpl::distinct_until_changed(
  211. ) | rpl::start_with_next([=](Settings::Types types) {
  212. mediaWrap->toggle((types & (Type::PersonalChats
  213. | Type::BotChats
  214. | Type::PrivateGroups
  215. | Type::PrivateChannels
  216. | Type::PublicGroups
  217. | Type::PublicChannels)) != 0, anim::type::normal);
  218. }, mediaWrap->lifetime());
  219. widthValue(
  220. ) | rpl::start_with_next([=](int width) {
  221. mediaWrap->resizeToWidth(width);
  222. }, mediaWrap->lifetime());
  223. }
  224. void SettingsWidget::setupOtherOptions(
  225. not_null<Ui::VerticalLayout*> container) {
  226. addHeader(container, tr::lng_export_header_other(tr::now));
  227. addOptionWithAbout(
  228. container,
  229. tr::lng_export_option_sessions(tr::now),
  230. Type::Sessions,
  231. tr::lng_export_option_sessions_about(tr::now));
  232. addOptionWithAbout(
  233. container,
  234. tr::lng_export_option_other(tr::now),
  235. Type::OtherData,
  236. tr::lng_export_option_other_about(tr::now));
  237. }
  238. void SettingsWidget::setupPathAndFormat(
  239. not_null<Ui::VerticalLayout*> container) {
  240. if (_singlePeerId != 0) {
  241. addFormatAndLocationLabel(container);
  242. addLimitsLabel(container);
  243. return;
  244. }
  245. const auto formatGroup = std::make_shared<Ui::RadioenumGroup<Format>>(
  246. readData().format);
  247. formatGroup->setChangedCallback([=](Format format) {
  248. changeData([&](Settings &data) {
  249. data.format = format;
  250. });
  251. });
  252. const auto addFormatOption = [&](QString label, Format format) {
  253. container->add(
  254. object_ptr<Ui::Radioenum<Format>>(
  255. container,
  256. formatGroup,
  257. format,
  258. label,
  259. st::defaultBoxCheckbox),
  260. st::exportSettingPadding);
  261. };
  262. addHeader(container, tr::lng_export_header_format(tr::now));
  263. addLocationLabel(container);
  264. addFormatOption(tr::lng_export_option_html(tr::now), Format::Html);
  265. addFormatOption(tr::lng_export_option_json(tr::now), Format::Json);
  266. addFormatOption(tr::lng_export_option_html_and_json(tr::now), Format::HtmlAndJson);
  267. }
  268. void SettingsWidget::addLocationLabel(
  269. not_null<Ui::VerticalLayout*> container) {
  270. #ifndef OS_MAC_STORE
  271. auto pathLink = value() | rpl::map([](const Settings &data) {
  272. return data.path;
  273. }) | rpl::distinct_until_changed(
  274. ) | rpl::map([=](const QString &path) {
  275. const auto text = IsDefaultPath(_session, path)
  276. ? Core::App().canReadDefaultDownloadPath()
  277. ? u"Downloads/"_q + File::DefaultDownloadPathFolder(_session)
  278. : tr::lng_download_path_temp(tr::now)
  279. : path;
  280. return Ui::Text::Link(
  281. QDir::toNativeSeparators(text),
  282. QString("internal:edit_export_path"));
  283. });
  284. const auto label = container->add(
  285. object_ptr<Ui::FlatLabel>(
  286. container,
  287. tr::lng_export_option_location(
  288. lt_path,
  289. std::move(pathLink),
  290. Ui::Text::WithEntities),
  291. st::exportLocationLabel),
  292. st::exportLocationPadding);
  293. label->overrideLinkClickHandler([=] {
  294. chooseFolder();
  295. });
  296. #endif // OS_MAC_STORE
  297. }
  298. void SettingsWidget::chooseFormat() {
  299. const auto shared = std::make_shared<QPointer<Ui::GenericBox>>();
  300. const auto callback = [=](Format format) {
  301. changeData([&](Settings &data) {
  302. data.format = format;
  303. });
  304. if (const auto weak = shared->data()) {
  305. weak->closeBox();
  306. }
  307. };
  308. auto box = Box(
  309. ChooseFormatBox,
  310. readData().format,
  311. callback);
  312. *shared = Ui::MakeWeak(box.data());
  313. _showBoxCallback(std::move(box));
  314. }
  315. void SettingsWidget::addFormatAndLocationLabel(
  316. not_null<Ui::VerticalLayout*> container) {
  317. #ifndef OS_MAC_STORE
  318. auto pathLink = value() | rpl::map([](const Settings &data) {
  319. return data.path;
  320. }) | rpl::distinct_until_changed(
  321. ) | rpl::map([=](const QString &path) {
  322. const auto text = IsDefaultPath(_session, path)
  323. ? Core::App().canReadDefaultDownloadPath()
  324. ? u"Downloads/"_q + File::DefaultDownloadPathFolder(_session)
  325. : tr::lng_download_path_temp(tr::now)
  326. : path;
  327. return Ui::Text::Link(
  328. QDir::toNativeSeparators(text),
  329. u"internal:edit_export_path"_q);
  330. });
  331. auto formatLink = value() | rpl::map([](const Settings &data) {
  332. return data.format;
  333. }) | rpl::distinct_until_changed(
  334. ) | rpl::map([](Format format) {
  335. const auto text = (format == Format::Html)
  336. ? "HTML"
  337. : (format == Format::Json)
  338. ? "JSON"
  339. : tr::lng_export_option_html_and_json(tr::now);
  340. return Ui::Text::Link(text, u"internal:edit_format"_q);
  341. });
  342. const auto label = container->add(
  343. object_ptr<Ui::FlatLabel>(
  344. container,
  345. tr::lng_export_option_format_location(
  346. lt_format,
  347. std::move(formatLink),
  348. lt_path,
  349. std::move(pathLink),
  350. Ui::Text::WithEntities),
  351. st::exportLocationLabel),
  352. st::exportLocationPadding);
  353. label->overrideLinkClickHandler([=](const QString &url) {
  354. if (url == u"internal:edit_export_path"_q) {
  355. chooseFolder();
  356. } else if (url == u"internal:edit_format"_q) {
  357. chooseFormat();
  358. } else {
  359. Unexpected("Click handler URL in export limits edit.");
  360. }
  361. });
  362. #endif // OS_MAC_STORE
  363. }
  364. void SettingsWidget::addLimitsLabel(
  365. not_null<Ui::VerticalLayout*> container) {
  366. auto fromDateLink = value() | rpl::map([](const Settings &data) {
  367. return data.singlePeerFrom;
  368. }) | rpl::distinct_until_changed(
  369. ) | rpl::map([](TimeId from) {
  370. return (from
  371. ? rpl::single(langDayOfMonthFull(
  372. base::unixtime::parse(from).date()))
  373. : tr::lng_export_beginning()
  374. ) | Ui::Text::ToLink(u"internal:edit_from"_q);
  375. }) | rpl::flatten_latest();
  376. const auto mapToTime = [](TimeId id, const QString &link) {
  377. return rpl::single(id
  378. ? QLocale().toString(
  379. base::unixtime::parse(id).time(),
  380. QLocale::ShortFormat)
  381. : QString()
  382. ) | Ui::Text::ToLink(link);
  383. };
  384. const auto concat = [](TextWithEntities date, TextWithEntities link) {
  385. return link.text.isEmpty()
  386. ? date
  387. : date.append(u", "_q).append(std::move(link));
  388. };
  389. auto fromTimeLink = value() | rpl::map([](const Settings &data) {
  390. return data.singlePeerFrom;
  391. }) | rpl::distinct_until_changed(
  392. ) | rpl::map([=](TimeId from) {
  393. return mapToTime(from, u"internal:edit_from_time"_q);
  394. }) | rpl::flatten_latest();
  395. auto fromLink = rpl::combine(
  396. std::move(fromDateLink),
  397. std::move(fromTimeLink)
  398. ) | rpl::map(concat);
  399. auto tillDateLink = value() | rpl::map([](const Settings &data) {
  400. return data.singlePeerTill;
  401. }) | rpl::distinct_until_changed(
  402. ) | rpl::map([](TimeId till) {
  403. return (till
  404. ? rpl::single(langDayOfMonthFull(
  405. base::unixtime::parse(till).date()))
  406. : tr::lng_export_end()
  407. ) | Ui::Text::ToLink(u"internal:edit_till"_q);
  408. }) | rpl::flatten_latest();
  409. auto tillTimeLink = value() | rpl::map([](const Settings &data) {
  410. return data.singlePeerTill;
  411. }) | rpl::distinct_until_changed(
  412. ) | rpl::map([=](TimeId till) {
  413. return mapToTime(till, u"internal:edit_till_time"_q);
  414. }) | rpl::flatten_latest();
  415. auto tillLink = rpl::combine(
  416. std::move(tillDateLink),
  417. std::move(tillTimeLink)
  418. ) | rpl::map(concat);
  419. auto datesText = tr::lng_export_limits(
  420. lt_from,
  421. std::move(fromLink),
  422. lt_till,
  423. std::move(tillLink),
  424. Ui::Text::WithEntities
  425. ) | rpl::after_next([=] {
  426. container->resizeToWidth(container->width());
  427. });
  428. const auto label = container->add(
  429. object_ptr<Ui::FlatLabel>(
  430. container,
  431. std::move(datesText),
  432. st::boxLabel),
  433. st::exportLimitsPadding);
  434. const auto removeTime = [](TimeId dateTime) {
  435. return base::unixtime::serialize(
  436. QDateTime(
  437. base::unixtime::parse(dateTime).date(),
  438. QTime()));
  439. };
  440. const auto editTimeLimit = [=](Fn<TimeId()> now, Fn<void(TimeId)> done) {
  441. _showBoxCallback(Box([=](not_null<Ui::GenericBox*> box) {
  442. auto result = Ui::ChooseTimeWidget(
  443. box->verticalLayout(),
  444. [&] {
  445. const auto time = base::unixtime::parse(now()).time();
  446. return time.hour() * 3600
  447. + time.minute() * 60
  448. + time.second();
  449. }(),
  450. true);
  451. const auto widget = box->addRow(std::move(result.widget));
  452. const auto toSave = widget->lifetime().make_state<TimeId>(0);
  453. std::move(
  454. result.secondsValue
  455. ) | rpl::start_with_next([=](TimeId t) {
  456. *toSave = t;
  457. }, box->lifetime());
  458. box->addButton(tr::lng_settings_save(), [=] {
  459. done(*toSave);
  460. box->closeBox();
  461. });
  462. box->addButton(tr::lng_cancel(), [=] {
  463. box->closeBox();
  464. });
  465. box->setTitle(tr::lng_settings_ttl_after_custom());
  466. }));
  467. };
  468. constexpr auto kOffset = 600;
  469. label->overrideLinkClickHandler([=](const QString &url) {
  470. if (url == u"internal:edit_from"_q) {
  471. const auto done = [=](TimeId limit) {
  472. changeData([&](Settings &settings) {
  473. settings.singlePeerFrom = limit;
  474. });
  475. };
  476. editDateLimit(
  477. readData().singlePeerFrom,
  478. 0,
  479. readData().singlePeerTill,
  480. tr::lng_export_from_beginning(),
  481. done);
  482. } else if (url == u"internal:edit_from_time"_q) {
  483. const auto now = [=] {
  484. auto result = TimeId(0);
  485. changeData([&](Settings &settings) {
  486. result = settings.singlePeerFrom;
  487. });
  488. return result;
  489. };
  490. const auto done = [=](TimeId time) {
  491. changeData([&](Settings &settings) {
  492. const auto result = time
  493. + removeTime(settings.singlePeerFrom);
  494. if (result >= settings.singlePeerTill
  495. && settings.singlePeerTill) {
  496. settings.singlePeerFrom = settings.singlePeerTill
  497. - kOffset;
  498. } else {
  499. settings.singlePeerFrom = result;
  500. }
  501. });
  502. };
  503. editTimeLimit(now, done);
  504. } else if (url == u"internal:edit_till"_q) {
  505. const auto done = [=](TimeId limit) {
  506. changeData([&](Settings &settings) {
  507. if (limit <= settings.singlePeerFrom
  508. && settings.singlePeerFrom) {
  509. settings.singlePeerTill = settings.singlePeerFrom
  510. + kOffset;
  511. } else {
  512. settings.singlePeerTill = limit;
  513. }
  514. });
  515. };
  516. editDateLimit(
  517. readData().singlePeerTill,
  518. readData().singlePeerFrom,
  519. 0,
  520. tr::lng_export_till_end(),
  521. done);
  522. } else if (url == u"internal:edit_till_time"_q) {
  523. const auto now = [=] {
  524. auto result = TimeId(0);
  525. changeData([&](Settings &settings) {
  526. result = settings.singlePeerTill;
  527. });
  528. return result;
  529. };
  530. const auto done = [=](TimeId time) {
  531. changeData([&](Settings &settings) {
  532. const auto result = time
  533. + removeTime(settings.singlePeerTill);
  534. if (result <= settings.singlePeerFrom
  535. && settings.singlePeerFrom) {
  536. settings.singlePeerTill = settings.singlePeerFrom
  537. + kOffset;
  538. } else {
  539. settings.singlePeerTill = result;
  540. }
  541. });
  542. };
  543. editTimeLimit(now, done);
  544. } else {
  545. Unexpected("Click handler URL in export limits edit.");
  546. }
  547. });
  548. }
  549. void SettingsWidget::editDateLimit(
  550. TimeId current,
  551. TimeId min,
  552. TimeId max,
  553. rpl::producer<QString> resetLabel,
  554. Fn<void(TimeId)> done) {
  555. Expects(_showBoxCallback != nullptr);
  556. const auto highlighted = current
  557. ? base::unixtime::parse(current).date()
  558. : max
  559. ? base::unixtime::parse(max).date()
  560. : min
  561. ? base::unixtime::parse(min).date()
  562. : QDate::currentDate();
  563. const auto month = highlighted;
  564. const auto shared = std::make_shared<QPointer<Ui::CalendarBox>>();
  565. const auto finalize = [=](not_null<Ui::CalendarBox*> box) {
  566. box->addLeftButton(std::move(resetLabel), crl::guard(this, [=] {
  567. done(0);
  568. if (const auto weak = shared->data()) {
  569. weak->closeBox();
  570. }
  571. }));
  572. };
  573. const auto callback = crl::guard(this, [=](const QDate &date) {
  574. done(base::unixtime::serialize(date.startOfDay()));
  575. if (const auto weak = shared->data()) {
  576. weak->closeBox();
  577. }
  578. });
  579. auto box = Box<Ui::CalendarBox>(Ui::CalendarBoxArgs{
  580. .month = month,
  581. .highlighted = highlighted,
  582. .callback = callback,
  583. .finalize = finalize,
  584. .st = st::exportCalendarSizes,
  585. .minDate = (min
  586. ? base::unixtime::parse(min).date()
  587. : QDate(2013, 8, 1)), // Telegram was launched in August 2013 :)
  588. .maxDate = (max
  589. ? base::unixtime::parse(max).date()
  590. : QDate::currentDate()),
  591. });
  592. *shared = Ui::MakeWeak(box.data());
  593. _showBoxCallback(std::move(box));
  594. }
  595. not_null<Ui::RpWidget*> SettingsWidget::setupButtons(
  596. not_null<Ui::ScrollArea*> scroll,
  597. not_null<Ui::RpWidget*> wrap) {
  598. using namespace rpl::mappers;
  599. const auto buttonsPadding = st::defaultBox.buttonPadding;
  600. const auto buttonsHeight = buttonsPadding.top()
  601. + st::defaultBoxButton.height
  602. + buttonsPadding.bottom();
  603. const auto buttons = Ui::CreateChild<Ui::FixedHeightWidget>(
  604. this,
  605. buttonsHeight);
  606. const auto topShadow = Ui::CreateChild<Ui::FadeShadow>(this);
  607. const auto bottomShadow = Ui::CreateChild<Ui::FadeShadow>(this);
  608. topShadow->toggleOn(scroll->scrollTopValue(
  609. ) | rpl::map(_1 > 0));
  610. bottomShadow->toggleOn(rpl::combine(
  611. scroll->heightValue(),
  612. scroll->scrollTopValue(),
  613. wrap->heightValue(),
  614. _2
  615. ) | rpl::map([=](int top) {
  616. return top < scroll->scrollTopMax();
  617. }));
  618. value() | rpl::map([](const Settings &data) {
  619. return (data.types != Types(0)) || data.onlySinglePeer();
  620. }) | rpl::distinct_until_changed(
  621. ) | rpl::start_with_next([=](bool canStart) {
  622. refreshButtons(buttons, canStart);
  623. topShadow->raise();
  624. bottomShadow->raise();
  625. }, buttons->lifetime());
  626. sizeValue(
  627. ) | rpl::start_with_next([=](QSize size) {
  628. buttons->resizeToWidth(size.width());
  629. buttons->moveToLeft(0, size.height() - buttons->height());
  630. topShadow->resizeToWidth(size.width());
  631. topShadow->moveToLeft(0, 0);
  632. bottomShadow->resizeToWidth(size.width());
  633. bottomShadow->moveToLeft(0, buttons->y() - st::lineWidth);
  634. }, buttons->lifetime());
  635. return buttons;
  636. }
  637. void SettingsWidget::addHeader(
  638. not_null<Ui::VerticalLayout*> container,
  639. const QString &text) {
  640. container->add(
  641. object_ptr<Ui::FlatLabel>(
  642. container,
  643. text,
  644. st::exportHeaderLabel),
  645. st::exportHeaderPadding);
  646. }
  647. not_null<Ui::Checkbox*> SettingsWidget::addOption(
  648. not_null<Ui::VerticalLayout*> container,
  649. const QString &text,
  650. Types types) {
  651. const auto checkbox = container->add(
  652. object_ptr<Ui::Checkbox>(
  653. container,
  654. text,
  655. ((readData().types & types) == types),
  656. st::defaultBoxCheckbox),
  657. st::exportSettingPadding);
  658. checkbox->checkedChanges(
  659. ) | rpl::start_with_next([=](bool checked) {
  660. changeData([&](Settings &data) {
  661. if (checked) {
  662. data.types |= types;
  663. } else {
  664. data.types &= ~types;
  665. }
  666. });
  667. }, checkbox->lifetime());
  668. return checkbox;
  669. }
  670. not_null<Ui::Checkbox*> SettingsWidget::addOptionWithAbout(
  671. not_null<Ui::VerticalLayout*> container,
  672. const QString &text,
  673. Types types,
  674. const QString &about) {
  675. const auto result = addOption(container, text, types);
  676. container->add(
  677. object_ptr<Ui::FlatLabel>(
  678. container,
  679. about,
  680. st::exportAboutOptionLabel),
  681. st::exportAboutOptionPadding);
  682. return result;
  683. }
  684. void SettingsWidget::addChatOption(
  685. not_null<Ui::VerticalLayout*> container,
  686. const QString &text,
  687. Types types) {
  688. const auto checkbox = addOption(container, text, types);
  689. const auto onlyMy = container->add(
  690. object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
  691. container,
  692. object_ptr<Ui::Checkbox>(
  693. container,
  694. tr::lng_export_option_only_my(tr::now),
  695. ((readData().fullChats & types) != types),
  696. st::defaultBoxCheckbox),
  697. st::exportSubSettingPadding));
  698. onlyMy->entity()->checkedChanges(
  699. ) | rpl::start_with_next([=](bool checked) {
  700. changeData([&](Settings &data) {
  701. if (checked) {
  702. data.fullChats &= ~types;
  703. } else {
  704. data.fullChats |= types;
  705. }
  706. });
  707. }, onlyMy->lifetime());
  708. onlyMy->toggleOn(checkbox->checkedValue());
  709. if (types & (Type::PublicGroups | Type::PublicChannels)) {
  710. onlyMy->entity()->setChecked(true);
  711. onlyMy->entity()->setDisabled(true);
  712. }
  713. }
  714. void SettingsWidget::addMediaOptions(
  715. not_null<Ui::VerticalLayout*> container) {
  716. addMediaOption(
  717. container,
  718. tr::lng_export_option_photos(tr::now),
  719. MediaType::Photo);
  720. addMediaOption(
  721. container,
  722. tr::lng_export_option_video_files(tr::now),
  723. MediaType::Video);
  724. addMediaOption(
  725. container,
  726. tr::lng_export_option_voice_messages(tr::now),
  727. MediaType::VoiceMessage);
  728. addMediaOption(
  729. container,
  730. tr::lng_export_option_video_messages(tr::now),
  731. MediaType::VideoMessage);
  732. addMediaOption(
  733. container,
  734. tr::lng_export_option_stickers(tr::now),
  735. MediaType::Sticker);
  736. addMediaOption(
  737. container,
  738. tr::lng_export_option_gifs(tr::now),
  739. MediaType::GIF);
  740. addMediaOption(
  741. container,
  742. tr::lng_export_option_files(tr::now),
  743. MediaType::File);
  744. addSizeSlider(container);
  745. }
  746. void SettingsWidget::addMediaOption(
  747. not_null<Ui::VerticalLayout*> container,
  748. const QString &text,
  749. MediaType type) {
  750. const auto checkbox = container->add(
  751. object_ptr<Ui::Checkbox>(
  752. container,
  753. text,
  754. ((readData().media.types & type) == type),
  755. st::defaultBoxCheckbox),
  756. st::exportSettingPadding);
  757. checkbox->checkedChanges(
  758. ) | rpl::start_with_next([=](bool checked) {
  759. changeData([&](Settings &data) {
  760. if (checked) {
  761. data.media.types |= type;
  762. } else {
  763. data.media.types &= ~type;
  764. }
  765. });
  766. }, checkbox->lifetime());
  767. }
  768. void SettingsWidget::addSizeSlider(
  769. not_null<Ui::VerticalLayout*> container) {
  770. using namespace rpl::mappers;
  771. const auto slider = container->add(
  772. object_ptr<Ui::MediaSlider>(container, st::exportFileSizeSlider),
  773. st::exportFileSizePadding);
  774. slider->resize(st::exportFileSizeSlider.seekSize);
  775. slider->setPseudoDiscrete(
  776. kSizeValueCount,
  777. SizeLimitByIndex,
  778. readData().media.sizeLimit,
  779. [=](int64 limit) {
  780. changeData([&](Settings &data) {
  781. data.media.sizeLimit = limit;
  782. });
  783. });
  784. const auto label = Ui::CreateChild<Ui::LabelSimple>(
  785. container.get(),
  786. st::exportFileSizeLabel);
  787. value() | rpl::map([](const Settings &data) {
  788. return data.media.sizeLimit;
  789. }) | rpl::start_with_next([=](int64 sizeLimit) {
  790. const auto limit = sizeLimit / kMegabyte;
  791. const auto size = QString::number(limit) + " MB";
  792. const auto text = tr::lng_export_option_size_limit(
  793. tr::now,
  794. lt_size,
  795. size);
  796. label->setText(text);
  797. }, slider->lifetime());
  798. rpl::combine(
  799. label->widthValue(),
  800. slider->geometryValue(),
  801. _2
  802. ) | rpl::start_with_next([=](QRect geometry) {
  803. label->moveToRight(
  804. st::exportFileSizePadding.right(),
  805. geometry.y() - label->height() - st::exportFileSizeLabelBottom);
  806. }, label->lifetime());
  807. }
  808. void SettingsWidget::refreshButtons(
  809. not_null<Ui::RpWidget*> container,
  810. bool canStart) {
  811. container->hideChildren();
  812. const auto children = container->children();
  813. for (const auto child : children) {
  814. if (child->isWidgetType()) {
  815. child->deleteLater();
  816. }
  817. }
  818. const auto start = canStart
  819. ? Ui::CreateChild<Ui::RoundButton>(
  820. container.get(),
  821. tr::lng_export_start(),
  822. st::defaultBoxButton)
  823. : nullptr;
  824. if (start) {
  825. start->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
  826. start->show();
  827. _startClicks = start->clicks() | rpl::to_empty;
  828. container->sizeValue(
  829. ) | rpl::start_with_next([=](QSize size) {
  830. const auto right = st::defaultBox.buttonPadding.right();
  831. const auto top = st::defaultBox.buttonPadding.top();
  832. start->moveToRight(right, top);
  833. }, start->lifetime());
  834. }
  835. const auto cancel = Ui::CreateChild<Ui::RoundButton>(
  836. container.get(),
  837. tr::lng_cancel(),
  838. st::defaultBoxButton);
  839. cancel->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
  840. cancel->show();
  841. _cancelClicks = cancel->clicks() | rpl::to_empty;
  842. rpl::combine(
  843. container->sizeValue(),
  844. start ? start->widthValue() : rpl::single(0)
  845. ) | rpl::start_with_next([=](QSize size, int width) {
  846. const auto right = st::defaultBox.buttonPadding.right()
  847. + (width ? width + st::defaultBox.buttonPadding.left() : 0);
  848. const auto top = st::defaultBox.buttonPadding.top();
  849. cancel->moveToRight(right, top);
  850. }, cancel->lifetime());
  851. }
  852. void SettingsWidget::chooseFolder() {
  853. const auto callback = [=](QString &&result) {
  854. changeData([&](Settings &data) {
  855. data.path = std::move(result);
  856. data.forceSubPath = IsDefaultPath(_session, data.path);
  857. });
  858. };
  859. FileDialog::GetFolder(
  860. this,
  861. tr::lng_export_folder(tr::now),
  862. readData().path,
  863. callback);
  864. }
  865. rpl::producer<Settings> SettingsWidget::changes() const {
  866. return _changes.events();
  867. }
  868. rpl::producer<Settings> SettingsWidget::value() const {
  869. return rpl::single(readData()) | rpl::then(changes());
  870. }
  871. rpl::producer<> SettingsWidget::startClicks() const {
  872. return _startClicks.value(
  873. ) | rpl::map([](Wrap &&wrap) {
  874. return std::move(wrap.value);
  875. }) | rpl::flatten_latest();
  876. }
  877. rpl::producer<> SettingsWidget::cancelClicks() const {
  878. return _cancelClicks.value(
  879. ) | rpl::map([](Wrap &&wrap) {
  880. return std::move(wrap.value);
  881. }) | rpl::flatten_latest();
  882. }
  883. } // namespace View
  884. } // namespace Export