background_preview_box.cpp 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171
  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/background_preview_box.h"
  8. #include "base/unixtime.h"
  9. #include "boxes/peers/edit_peer_color_box.h"
  10. #include "boxes/premium_preview_box.h"
  11. #include "lang/lang_keys.h"
  12. #include "mainwidget.h"
  13. #include "window/themes/window_theme.h"
  14. #include "ui/boxes/confirm_box.h"
  15. #include "ui/boxes/boost_box.h"
  16. #include "ui/controls/chat_service_checkbox.h"
  17. #include "ui/chat/chat_theme.h"
  18. #include "ui/chat/chat_style.h"
  19. #include "ui/toast/toast.h"
  20. #include "ui/image/image.h"
  21. #include "ui/widgets/checkbox.h"
  22. #include "ui/widgets/continuous_sliders.h"
  23. #include "ui/wrap/fade_wrap.h"
  24. #include "ui/wrap/slide_wrap.h"
  25. #include "ui/painter.h"
  26. #include "ui/vertical_list.h"
  27. #include "ui/ui_utility.h"
  28. #include "history/history.h"
  29. #include "history/history_item.h"
  30. #include "history/history_item_helpers.h"
  31. #include "history/view/history_view_message.h"
  32. #include "main/main_session.h"
  33. #include "apiwrap.h"
  34. #include "data/data_session.h"
  35. #include "data/data_user.h"
  36. #include "data/data_document.h"
  37. #include "data/data_document_media.h"
  38. #include "data/data_document_resolver.h"
  39. #include "data/data_file_origin.h"
  40. #include "data/data_peer_values.h"
  41. #include "data/data_premium_limits.h"
  42. #include "settings/settings_premium.h"
  43. #include "storage/file_upload.h"
  44. #include "storage/localimageloader.h"
  45. #include "window/window_session_controller.h"
  46. #include "window/themes/window_themes_embedded.h"
  47. #include "styles/style_chat.h"
  48. #include "styles/style_layers.h"
  49. #include "styles/style_boxes.h"
  50. #include <QtGui/QClipboard>
  51. #include <QtGui/QGuiApplication>
  52. namespace {
  53. constexpr auto kMaxWallPaperSlugLength = 255;
  54. [[nodiscard]] bool IsValidWallPaperSlug(const QString &slug) {
  55. if (slug.isEmpty() || slug.size() > kMaxWallPaperSlugLength) {
  56. return false;
  57. }
  58. return ranges::none_of(slug, [](QChar ch) {
  59. return (ch != '.')
  60. && (ch != '_')
  61. && (ch != '-')
  62. && (ch < '0' || ch > '9')
  63. && (ch < 'a' || ch > 'z')
  64. && (ch < 'A' || ch > 'Z');
  65. });
  66. }
  67. [[nodiscard]] AdminLog::OwnedItem GenerateServiceItem(
  68. not_null<HistoryView::ElementDelegate*> delegate,
  69. not_null<History*> history,
  70. const QString &text,
  71. bool out) {
  72. Expects(history->peer->isUser());
  73. const auto flags = MessageFlag::FakeHistoryItem
  74. | MessageFlag::HasFromId
  75. | (out ? MessageFlag::Outgoing : MessageFlag(0));
  76. const auto item = history->makeMessage({
  77. .id = history->owner().nextLocalMessageId(),
  78. .flags = flags,
  79. .date = base::unixtime::now(),
  80. }, PreparedServiceText{ { text } });
  81. return AdminLog::OwnedItem(delegate, item);
  82. }
  83. [[nodiscard]] AdminLog::OwnedItem GenerateTextItem(
  84. not_null<HistoryView::ElementDelegate*> delegate,
  85. not_null<History*> history,
  86. const QString &text,
  87. bool out) {
  88. Expects(history->peer->isUser());
  89. const auto item = history->makeMessage({
  90. .id = history->nextNonHistoryEntryId(),
  91. .flags = (MessageFlag::FakeHistoryItem
  92. | MessageFlag::HasFromId
  93. | (out ? MessageFlag::Outgoing : MessageFlag(0))),
  94. .from = (out
  95. ? history->session().userId()
  96. : peerToUser(history->peer->id)),
  97. .date = base::unixtime::now(),
  98. }, TextWithEntities{ text }, MTP_messageMediaEmpty());
  99. return AdminLog::OwnedItem(delegate, item);
  100. }
  101. [[nodiscard]] QImage PrepareScaledNonPattern(
  102. const QImage &image,
  103. Images::Option blur) {
  104. const auto size = st::boxWideWidth;
  105. const auto width = std::max(image.width(), 1);
  106. const auto height = std::max(image.height(), 1);
  107. const auto takeWidth = (width > height)
  108. ? (width * size / height)
  109. : size;
  110. const auto takeHeight = (width > height)
  111. ? size
  112. : (height * size / width);
  113. const auto ratio = style::DevicePixelRatio();
  114. return Images::Prepare(image, QSize(takeWidth, takeHeight) * ratio, {
  115. .options = Images::Option::TransparentBackground | blur,
  116. .outer = { size, size },
  117. });
  118. }
  119. [[nodiscard]] QImage PrepareScaledFromFull(
  120. const QImage &image,
  121. bool isPattern,
  122. const std::vector<QColor> &background,
  123. int gradientRotation,
  124. float64 patternOpacity,
  125. Images::Option blur = Images::Option(0)) {
  126. auto result = PrepareScaledNonPattern(image, blur);
  127. if (isPattern) {
  128. result = Ui::PreparePatternImage(
  129. std::move(result),
  130. background,
  131. gradientRotation,
  132. patternOpacity);
  133. }
  134. return std::move(result).convertToFormat(
  135. QImage::Format_ARGB32_Premultiplied);
  136. }
  137. [[nodiscard]] QImage BlackImage(QSize size) {
  138. auto result = QImage(size, QImage::Format_ARGB32_Premultiplied);
  139. result.fill(Qt::black);
  140. return result;
  141. }
  142. [[nodiscard]] Data::WallPaper Resolve(
  143. not_null<Main::Session*> session,
  144. const Data::WallPaper &paper,
  145. bool dark) {
  146. if (paper.emojiId().isEmpty()) {
  147. return paper;
  148. }
  149. const auto &themes = session->data().cloudThemes();
  150. if (const auto theme = themes.themeForEmoji(paper.emojiId())) {
  151. using Type = Data::CloudThemeType;
  152. const auto type = dark ? Type::Dark : Type::Light;
  153. const auto i = theme->settings.find(type);
  154. if (i != end(theme->settings) && i->second.paper) {
  155. return *i->second.paper;
  156. }
  157. }
  158. return paper;
  159. }
  160. } // namespace
  161. struct BackgroundPreviewBox::OverridenStyle {
  162. style::Box box;
  163. style::IconButton toggle;
  164. style::MediaSlider slider;
  165. style::FlatLabel subtitle;
  166. };
  167. BackgroundPreviewBox::BackgroundPreviewBox(
  168. QWidget*,
  169. not_null<Window::SessionController*> controller,
  170. const Data::WallPaper &paper,
  171. BackgroundPreviewArgs args)
  172. : SimpleElementDelegate(controller, [=] { update(); })
  173. , _controller(controller)
  174. , _forPeer(args.forPeer)
  175. , _fromMessageId(args.fromMessageId)
  176. , _chatStyle(std::make_unique<Ui::ChatStyle>(
  177. controller->session().colorIndicesValue()))
  178. , _serviceHistory(_controller->session().data().history(
  179. PeerData::kServiceNotificationsId))
  180. , _service(nullptr)
  181. , _text1(GenerateTextItem(
  182. delegate(),
  183. _serviceHistory,
  184. (_forPeer
  185. ? tr::lng_background_apply1(tr::now)
  186. : tr::lng_background_text1(tr::now)),
  187. false))
  188. , _text2(GenerateTextItem(
  189. delegate(),
  190. _serviceHistory,
  191. (_forPeer
  192. ? tr::lng_background_apply2(tr::now)
  193. : tr::lng_background_text2(tr::now)),
  194. true))
  195. , _paperEmojiId(paper.emojiId())
  196. , _paper(
  197. Resolve(&controller->session(), paper, Window::Theme::IsNightMode()))
  198. , _media(_paper.document() ? _paper.document()->createMediaView() : nullptr)
  199. , _radial([=](crl::time now) { radialAnimationCallback(now); })
  200. , _appNightMode(Window::Theme::IsNightModeValue())
  201. , _boxDarkMode(_appNightMode.current())
  202. , _dimmingIntensity(std::clamp(_paper.patternIntensity(), 0, 100))
  203. , _dimmed(_forPeer
  204. && (_paper.document() || _paper.localThumbnail())
  205. && !_paper.isPattern()) {
  206. if (_media) {
  207. _media->thumbnailWanted(_paper.fileOrigin());
  208. }
  209. generateBackground();
  210. _controller->session().downloaderTaskFinished(
  211. ) | rpl::start_with_next([=] {
  212. update();
  213. }, lifetime());
  214. _appNightMode.changes(
  215. ) | rpl::start_with_next([=](bool night) {
  216. _boxDarkMode = night;
  217. update();
  218. }, lifetime());
  219. _boxDarkMode.changes(
  220. ) | rpl::start_with_next([=](bool dark) {
  221. applyDarkMode(dark);
  222. }, lifetime());
  223. const auto prepare = [=](bool dark, auto pointer) {
  224. const auto weak = Ui::MakeWeak(this);
  225. crl::async([=] {
  226. auto result = std::make_unique<style::palette>();
  227. Window::Theme::PreparePaletteCallback(dark, {})(*result);
  228. crl::on_main([=, result = std::move(result)]() mutable {
  229. if (const auto strong = weak.data()) {
  230. strong->*pointer = std::move(result);
  231. strong->paletteReady();
  232. }
  233. });
  234. });
  235. };
  236. prepare(false, &BackgroundPreviewBox::_lightPalette);
  237. prepare(true, &BackgroundPreviewBox::_darkPalette);
  238. }
  239. BackgroundPreviewBox::~BackgroundPreviewBox() = default;
  240. void BackgroundPreviewBox::recreate(bool dark) {
  241. _paper = Resolve(
  242. &_controller->session(),
  243. Data::WallPaper::FromEmojiId(_paperEmojiId),
  244. dark);
  245. _media = _paper.document()
  246. ? _paper.document()->createMediaView()
  247. : nullptr;
  248. if (_media) {
  249. _media->thumbnailWanted(_paper.fileOrigin());
  250. }
  251. _full = QImage();
  252. _generated = _scaled = _blurred = _fadeOutThumbnail = QPixmap();
  253. _generating = {};
  254. generateBackground();
  255. _paper.loadDocument();
  256. if (const auto document = _paper.document()) {
  257. if (document->loading()) {
  258. _radial.start(_media->progress());
  259. }
  260. }
  261. checkLoadedDocument();
  262. updateServiceBg(_paper.backgroundColors());
  263. update();
  264. }
  265. void BackgroundPreviewBox::applyDarkMode(bool dark) {
  266. if (!_paperEmojiId.isEmpty()) {
  267. recreate(dark);
  268. }
  269. const auto equals = (dark == Window::Theme::IsNightMode());
  270. const auto &palette = (dark ? _darkPalette : _lightPalette);
  271. if (!equals && !palette) {
  272. _waitingForPalette = true;
  273. return;
  274. }
  275. _waitingForPalette = false;
  276. if (equals) {
  277. setStyle(st::defaultBox);
  278. _chatStyle->applyCustomPalette(nullptr);
  279. _paletteServiceBg = rpl::single(
  280. rpl::empty
  281. ) | rpl::then(
  282. style::PaletteChanged()
  283. ) | rpl::map([=] {
  284. return st::msgServiceBg->c;
  285. });
  286. } else {
  287. setStyle(overridenStyle(dark));
  288. _chatStyle->applyCustomPalette(palette.get());
  289. _paletteServiceBg = palette->msgServiceBg()->c;
  290. }
  291. resetTitle();
  292. rebuildButtons(dark);
  293. update();
  294. if (const auto parent = parentWidget()) {
  295. parent->update();
  296. }
  297. if (_dimmed) {
  298. createDimmingSlider(dark);
  299. }
  300. }
  301. void BackgroundPreviewBox::createDimmingSlider(bool dark) {
  302. const auto created = !_dimmingWrap;
  303. if (created) {
  304. _dimmingWrap.create(this, object_ptr<Ui::RpWidget>(this));
  305. _dimmingContent = _dimmingWrap->entity();
  306. }
  307. _dimmingSlider = nullptr;
  308. for (const auto &child : _dimmingContent->children()) {
  309. if (child->isWidgetType()) {
  310. static_cast<QWidget*>(child)->hide();
  311. child->deleteLater();
  312. }
  313. }
  314. const auto equals = (dark == Window::Theme::IsNightMode());
  315. const auto inner = Ui::CreateChild<Ui::VerticalLayout>(_dimmingContent);
  316. inner->show();
  317. Ui::AddSubsectionTitle(
  318. inner,
  319. tr::lng_background_dimming(),
  320. style::margins(0, st::defaultVerticalListSkip, 0, 0),
  321. equals ? nullptr : dark ? &_dark->subtitle : &_light->subtitle);
  322. _dimmingSlider = inner->add(
  323. object_ptr<Ui::MediaSlider>(
  324. inner,
  325. (equals
  326. ? st::defaultContinuousSlider
  327. : dark
  328. ? _dark->slider
  329. : _light->slider)),
  330. st::localStorageLimitMargin);
  331. _dimmingSlider->setValue(_dimmingIntensity / 100.);
  332. _dimmingSlider->setAlwaysDisplayMarker(true);
  333. _dimmingSlider->resize(st::defaultContinuousSlider.seekSize);
  334. const auto handle = [=](float64 value) {
  335. const auto intensity = std::clamp(
  336. int(base::SafeRound(value * 100)),
  337. 0,
  338. 100);
  339. _paper = _paper.withPatternIntensity(intensity);
  340. _dimmingIntensity = intensity;
  341. update();
  342. };
  343. _dimmingSlider->setChangeProgressCallback(handle);
  344. _dimmingSlider->setChangeFinishedCallback(handle);
  345. inner->resizeToWidth(st::boxWideWidth);
  346. Ui::SendPendingMoveResizeEvents(inner);
  347. inner->move(0, 0);
  348. _dimmingContent->resize(inner->size());
  349. _dimmingContent->paintRequest(
  350. ) | rpl::start_with_next([=](QRect clip) {
  351. auto p = QPainter(_dimmingContent);
  352. const auto palette = (dark ? _darkPalette : _lightPalette).get();
  353. p.fillRect(clip, equals ? st::boxBg : palette->boxBg());
  354. }, _dimmingContent->lifetime());
  355. _dimmingToggleScheduled = true;
  356. if (created) {
  357. rpl::combine(
  358. heightValue(),
  359. _dimmingWrap->heightValue(),
  360. rpl::mappers::_1 - rpl::mappers::_2
  361. ) | rpl::start_with_next([=](int top) {
  362. _dimmingWrap->move(0, top);
  363. }, _dimmingWrap->lifetime());
  364. _dimmingWrap->toggle(dark, anim::type::instant);
  365. _dimmingHeight = _dimmingWrap->heightValue();
  366. _dimmingHeight.changes() | rpl::start_with_next([=] {
  367. update();
  368. }, _dimmingWrap->lifetime());
  369. }
  370. }
  371. void BackgroundPreviewBox::paletteReady() {
  372. if (_waitingForPalette) {
  373. applyDarkMode(_boxDarkMode.current());
  374. }
  375. }
  376. const style::Box &BackgroundPreviewBox::overridenStyle(bool dark) {
  377. auto &st = dark ? _dark : _light;
  378. if (!st) {
  379. st = std::make_unique<OverridenStyle>(prepareOverridenStyle(dark));
  380. }
  381. return st->box;
  382. }
  383. auto BackgroundPreviewBox::prepareOverridenStyle(bool dark)
  384. -> OverridenStyle {
  385. const auto p = (dark ? _darkPalette : _lightPalette).get();
  386. Assert(p != nullptr);
  387. const auto &toggle = dark
  388. ? st::backgroundSwitchToLight
  389. : st::backgroundSwitchToDark;
  390. auto result = OverridenStyle{
  391. .box = st::defaultBox,
  392. .toggle = toggle,
  393. .slider = st::defaultContinuousSlider,
  394. .subtitle = st::defaultSubsectionTitle,
  395. };
  396. result.box.button.textFg = p->lightButtonFg();
  397. result.box.button.textFgOver = p->lightButtonFgOver();
  398. result.box.button.numbersTextFg = p->lightButtonFg();
  399. result.box.button.numbersTextFgOver = p->lightButtonFgOver();
  400. result.box.button.textBg = p->lightButtonBg();
  401. result.box.button.textBgOver = p->lightButtonBgOver();
  402. result.box.button.ripple.color = p->lightButtonBgRipple();
  403. result.box.title.textFg = p->boxTitleFg();
  404. result.box.bg = p->boxBg();
  405. result.box.titleAdditionalFg = p->boxTitleAdditionalFg();
  406. result.toggle.ripple.color = p->windowBgOver();
  407. result.toggle.icon = toggle.icon.withPalette(*p);
  408. result.toggle.iconOver = toggle.iconOver.withPalette(*p);
  409. result.slider.activeFg = p->mediaPlayerActiveFg();
  410. result.slider.inactiveFg = p->mediaPlayerInactiveFg();
  411. result.slider.activeFgOver = p->mediaPlayerActiveFg();
  412. result.slider.inactiveFgOver = p->mediaPlayerInactiveFg();
  413. result.slider.activeFgDisabled = p->mediaPlayerInactiveFg();
  414. result.slider.inactiveFgDisabled = p->windowBg();
  415. result.slider.receivedTillFg = p->mediaPlayerInactiveFg();
  416. result.subtitle.textFg = p->windowActiveTextFg();
  417. return result;
  418. }
  419. bool BackgroundPreviewBox::forChannel() const {
  420. return _forPeer && _forPeer->isChannel();
  421. }
  422. bool BackgroundPreviewBox::forGroup() const {
  423. return forChannel() && _forPeer->isMegagroup();
  424. }
  425. void BackgroundPreviewBox::generateBackground() {
  426. if (_paper.backgroundColors().empty()) {
  427. return;
  428. }
  429. const auto size = QSize(st::boxWideWidth, st::boxWideWidth)
  430. * style::DevicePixelRatio();
  431. _generated = Ui::PixmapFromImage((_paper.patternOpacity() >= 0.)
  432. ? Ui::GenerateBackgroundImage(
  433. size,
  434. _paper.backgroundColors(),
  435. _paper.gradientRotation())
  436. : BlackImage(size));
  437. _generated.setDevicePixelRatio(style::DevicePixelRatio());
  438. }
  439. not_null<HistoryView::ElementDelegate*> BackgroundPreviewBox::delegate() {
  440. return static_cast<HistoryView::ElementDelegate*>(this);
  441. }
  442. void BackgroundPreviewBox::resetTitle() {
  443. setTitle(tr::lng_background_header());
  444. }
  445. void BackgroundPreviewBox::rebuildButtons(bool dark) {
  446. clearButtons();
  447. addButton(forGroup()
  448. ? tr::lng_background_apply_group()
  449. : forChannel()
  450. ? tr::lng_background_apply_channel()
  451. : _forPeer
  452. ? tr::lng_background_apply_button()
  453. : tr::lng_settings_apply(), [=] { apply(); });
  454. addButton(tr::lng_cancel(), [=] { closeBox(); });
  455. if (!_forPeer && _paper.hasShareUrl()) {
  456. addLeftButton(tr::lng_background_share(), [=] { share(); });
  457. }
  458. const auto equals = (dark == Window::Theme::IsNightMode());
  459. auto toggle = object_ptr<Ui::IconButton>(this, equals
  460. ? (dark ? st::backgroundSwitchToLight : st::backgroundSwitchToDark)
  461. : dark ? _dark->toggle : _light->toggle);
  462. toggle->setClickedCallback([=] {
  463. _boxDarkMode = !_boxDarkMode.current();
  464. });
  465. addTopButton(std::move(toggle));
  466. }
  467. void BackgroundPreviewBox::prepare() {
  468. applyDarkMode(Window::Theme::IsNightMode());
  469. _paper.loadDocument();
  470. if (const auto document = _paper.document()) {
  471. if (document->loading()) {
  472. _radial.start(_media->progress());
  473. }
  474. }
  475. updateServiceBg(_paper.backgroundColors());
  476. setScaledFromThumb();
  477. checkLoadedDocument();
  478. _text1->setDisplayDate(false);
  479. _text1->initDimensions();
  480. _text1->resizeGetHeight(st::boxWideWidth);
  481. _text2->initDimensions();
  482. _text2->resizeGetHeight(st::boxWideWidth);
  483. setDimensions(st::boxWideWidth, st::boxWideWidth);
  484. }
  485. void BackgroundPreviewBox::recreateBlurCheckbox() {
  486. const auto document = _paper.document();
  487. if (_paper.isPattern()
  488. || (!_paper.localThumbnail()
  489. && (!document || !document->hasThumbnail()))) {
  490. return;
  491. }
  492. const auto blurred = _blur ? _blur->checked() : _paper.isBlurred();
  493. _blur = Ui::MakeChatServiceCheckbox(
  494. this,
  495. tr::lng_background_blur(tr::now),
  496. st::backgroundCheckbox,
  497. st::backgroundCheck,
  498. blurred,
  499. [=] { return _serviceBg.value_or(QColor(255, 255, 255, 0)); });
  500. _blur->show();
  501. rpl::combine(
  502. sizeValue(),
  503. _blur->sizeValue(),
  504. _dimmingHeight.value()
  505. ) | rpl::start_with_next([=](QSize outer, QSize inner, int dimming) {
  506. const auto bottom = st::historyPaddingBottom;
  507. _blur->move(
  508. (outer.width() - inner.width()) / 2,
  509. outer.height() - dimming - bottom - inner.height());
  510. }, _blur->lifetime());
  511. _blur->checkedChanges(
  512. ) | rpl::start_with_next([=](bool checked) {
  513. checkBlurAnimationStart();
  514. update();
  515. }, _blur->lifetime());
  516. _blur->setDisabled(_paper.document() && _full.isNull());
  517. if (_forBothOverlay) {
  518. _forBothOverlay->raise();
  519. }
  520. }
  521. void BackgroundPreviewBox::apply() {
  522. if (_forPeer) {
  523. applyForPeer();
  524. } else {
  525. applyForEveryone();
  526. }
  527. }
  528. void BackgroundPreviewBox::uploadForPeer(bool both) {
  529. Expects(_forPeer != nullptr);
  530. if (_uploadId) {
  531. return;
  532. }
  533. const auto session = &_controller->session();
  534. const auto ready = Window::Theme::PrepareWallPaper(
  535. session->mainDcId(),
  536. _paper.localThumbnail()->original());
  537. const auto documentId = ready->id;
  538. _uploadId = FullMsgId(
  539. session->userPeerId(),
  540. session->data().nextLocalMessageId());
  541. session->uploader().upload(_uploadId, ready);
  542. if (_uploadLifetime) {
  543. return;
  544. }
  545. const auto document = session->data().document(documentId);
  546. document->uploadingData = std::make_unique<Data::UploadState>(
  547. document->size);
  548. session->uploader().documentProgress(
  549. ) | rpl::start_with_next([=](const FullMsgId &fullId) {
  550. if (fullId != _uploadId) {
  551. return;
  552. }
  553. _uploadProgress = document->uploading()
  554. ? ((document->uploadingData->offset * 100)
  555. / document->uploadingData->size)
  556. : 0.;
  557. update(radialRect());
  558. }, _uploadLifetime);
  559. session->uploader().documentReady(
  560. ) | rpl::start_with_next([=](const Storage::UploadedMedia &data) {
  561. if (data.fullId != _uploadId) {
  562. return;
  563. }
  564. _uploadProgress = 1.;
  565. _uploadLifetime.destroy();
  566. update(radialRect());
  567. session->api().request(MTPaccount_UploadWallPaper(
  568. MTP_flags(MTPaccount_UploadWallPaper::Flag::f_for_chat),
  569. data.info.file,
  570. MTP_string("image/jpeg"),
  571. _paper.mtpSettings()
  572. )).done([=](const MTPWallPaper &result) {
  573. result.match([&](const MTPDwallPaper &data) {
  574. session->data().documentConvert(
  575. session->data().document(documentId),
  576. data.vdocument());
  577. }, [&](const MTPDwallPaperNoFile &data) {
  578. LOG(("API Error: "
  579. "Got wallPaperNoFile after account.UploadWallPaper."));
  580. });
  581. if (const auto paper = Data::WallPaper::Create(session, result)) {
  582. setExistingForPeer(*paper, both);
  583. }
  584. }).send();
  585. }, _uploadLifetime);
  586. _uploadProgress = 0.;
  587. _radial.start(_uploadProgress);
  588. }
  589. void BackgroundPreviewBox::setExistingForPeer(
  590. const Data::WallPaper &paper,
  591. bool both) {
  592. Expects(_forPeer != nullptr);
  593. if (const auto already = _forPeer->wallPaper()) {
  594. if (already->equals(paper)) {
  595. _controller->finishChatThemeEdit(_forPeer);
  596. return;
  597. }
  598. }
  599. const auto api = &_controller->session().api();
  600. using Flag = MTPmessages_SetChatWallPaper::Flag;
  601. api->request(MTPmessages_SetChatWallPaper(
  602. MTP_flags((_fromMessageId ? Flag::f_id : Flag())
  603. | (_fromMessageId ? Flag() : Flag::f_wallpaper)
  604. | (both ? Flag::f_for_both : Flag())
  605. | Flag::f_settings),
  606. _forPeer->input,
  607. paper.mtpInput(&_controller->session()),
  608. paper.mtpSettings(),
  609. MTP_int(_fromMessageId.msg)
  610. )).done([=](const MTPUpdates &result) {
  611. api->applyUpdates(result);
  612. }).send();
  613. _forPeer->setWallPaper(paper);
  614. _controller->finishChatThemeEdit(_forPeer);
  615. }
  616. void BackgroundPreviewBox::checkLevelForChannel() {
  617. Expects(forChannel());
  618. const auto show = _controller->uiShow();
  619. _forPeerLevelCheck = true;
  620. const auto weak = Ui::MakeWeak(this);
  621. CheckBoostLevel(show, _forPeer, [=](int level) {
  622. if (!weak) {
  623. return std::optional<Ui::AskBoostReason>();
  624. }
  625. const auto limits = Data::LevelLimits(&_forPeer->session());
  626. const auto required = _paperEmojiId.isEmpty()
  627. ? limits.channelCustomWallpaperLevelMin()
  628. : limits.channelWallpaperLevelMin();
  629. if (level >= required) {
  630. applyForPeer(false);
  631. return std::optional<Ui::AskBoostReason>();
  632. }
  633. return std::make_optional(Ui::AskBoostReason{
  634. Ui::AskBoostWallpaper{ required, _forPeer->isMegagroup()}
  635. });
  636. }, [=] { _forPeerLevelCheck = false; });
  637. }
  638. void BackgroundPreviewBox::applyForPeer() {
  639. Expects(_forPeer != nullptr);
  640. if (!Data::IsCustomWallPaper(_paper)) {
  641. if (const auto already = _forPeer->wallPaper()) {
  642. if (already->equals(_paper)) {
  643. _controller->finishChatThemeEdit(_forPeer);
  644. return;
  645. }
  646. }
  647. }
  648. if (forChannel()) {
  649. checkLevelForChannel();
  650. return;
  651. } else if (_fromMessageId || !_forPeer->session().premiumPossible()) {
  652. applyForPeer(false);
  653. return;
  654. } else if (_forBothOverlay) {
  655. return;
  656. }
  657. const auto size = this->size() * style::DevicePixelRatio();
  658. const auto bg = Images::DitherImage(
  659. Images::BlurLargeImage(
  660. Ui::GrabWidgetToImage(this).scaled(
  661. size / style::ConvertScale(4),
  662. Qt::IgnoreAspectRatio,
  663. Qt::SmoothTransformation),
  664. 24).scaled(
  665. size,
  666. Qt::IgnoreAspectRatio,
  667. Qt::SmoothTransformation));
  668. _forBothOverlay = std::make_unique<Ui::FadeWrap<>>(
  669. this,
  670. object_ptr<Ui::RpWidget>(this));
  671. const auto overlay = _forBothOverlay->entity();
  672. sizeValue() | rpl::start_with_next([=](QSize size) {
  673. _forBothOverlay->setGeometry({ QPoint(), size });
  674. overlay->setGeometry({ QPoint(), size });
  675. }, _forBothOverlay->lifetime());
  676. overlay->paintRequest(
  677. ) | rpl::start_with_next([=](QRect clip) {
  678. auto p = QPainter(overlay);
  679. p.drawImage(0, 0, bg);
  680. p.fillRect(clip, QColor(0, 0, 0, 64));
  681. }, overlay->lifetime());
  682. using namespace Ui;
  683. const auto forMe = CreateChild<RoundButton>(
  684. overlay,
  685. tr::lng_background_apply_me(),
  686. st::backgroundConfirm);
  687. forMe->setClickedCallback([=] {
  688. applyForPeer(false);
  689. });
  690. using namespace rpl::mappers;
  691. const auto forBoth = ::Settings::CreateLockedButton(
  692. overlay,
  693. tr::lng_background_apply_both(
  694. lt_user,
  695. rpl::single(_forPeer->shortName())),
  696. st::backgroundConfirm,
  697. Data::AmPremiumValue(&_forPeer->session()) | rpl::map(!_1));
  698. forBoth->setClickedCallback([=] {
  699. if (_forPeer->session().premium()) {
  700. applyForPeer(true);
  701. } else {
  702. ShowPremiumPreviewBox(
  703. _controller->uiShow(),
  704. PremiumFeature::Wallpapers);
  705. }
  706. });
  707. const auto cancel = CreateChild<RoundButton>(
  708. overlay,
  709. tr::lng_cancel(),
  710. st::backgroundConfirmCancel);
  711. cancel->setClickedCallback([=] {
  712. const auto raw = _forBothOverlay.release();
  713. raw->shownValue() | rpl::filter(
  714. !rpl::mappers::_1
  715. ) | rpl::take(1) | rpl::start_with_next(crl::guard(raw, [=] {
  716. delete raw;
  717. }), raw->lifetime());
  718. raw->toggle(false, anim::type::normal);
  719. });
  720. forMe->setTextTransform(RoundButton::TextTransform::NoTransform);
  721. forBoth->setTextTransform(RoundButton::TextTransform::NoTransform);
  722. cancel->setTextTransform(RoundButton::TextTransform::NoTransform);
  723. overlay->sizeValue(
  724. ) | rpl::start_with_next([=](QSize size) {
  725. const auto padding = st::backgroundConfirmPadding;
  726. const auto width = size.width()
  727. - padding.left()
  728. - padding.right();
  729. const auto height = cancel->height();
  730. auto top = size.height() - padding.bottom() - height;
  731. cancel->setGeometry(padding.left(), top, width, height);
  732. top -= height + padding.top();
  733. forBoth->setGeometry(padding.left(), top, width, height);
  734. top -= height + padding.top();
  735. forMe->setGeometry(padding.left(), top, width, height);
  736. }, _forBothOverlay->lifetime());
  737. _forBothOverlay->hide(anim::type::instant);
  738. _forBothOverlay->show(anim::type::normal);
  739. }
  740. void BackgroundPreviewBox::applyForPeer(bool both) {
  741. using namespace Data;
  742. if (forChannel() && !_paperEmojiId.isEmpty()) {
  743. setExistingForPeer(WallPaper::FromEmojiId(_paperEmojiId), both);
  744. } else if (IsCustomWallPaper(_paper)) {
  745. uploadForPeer(both);
  746. } else {
  747. setExistingForPeer(_paper, both);
  748. }
  749. }
  750. void BackgroundPreviewBox::applyForEveryone() {
  751. const auto install = (_paper.id() != Window::Theme::Background()->id())
  752. && Data::IsCloudWallPaper(_paper);
  753. _controller->content()->setChatBackground(_paper, std::move(_full));
  754. if (install) {
  755. _controller->session().api().request(MTPaccount_InstallWallPaper(
  756. _paper.mtpInput(&_controller->session()),
  757. _paper.mtpSettings()
  758. )).send();
  759. }
  760. closeBox();
  761. }
  762. void BackgroundPreviewBox::share() {
  763. QGuiApplication::clipboard()->setText(
  764. _paper.shareUrl(&_controller->session()));
  765. showToast(tr::lng_background_link_copied(tr::now));
  766. }
  767. void BackgroundPreviewBox::paintEvent(QPaintEvent *e) {
  768. Painter p(this);
  769. const auto ms = crl::now();
  770. if (_scaled.isNull()) {
  771. setScaledFromThumb();
  772. }
  773. if (!_generated.isNull()
  774. && (_scaled.isNull()
  775. || (_fadeOutThumbnail.isNull() && _fadeIn.animating()))) {
  776. p.drawPixmap(0, 0, _generated);
  777. }
  778. if (!_scaled.isNull()) {
  779. paintImage(p);
  780. const auto dimming = (_dimmed && _boxDarkMode.current())
  781. ? _dimmingIntensity
  782. : 0;
  783. if (dimming > 0) {
  784. const auto alpha = 255 * dimming / 100;
  785. p.fillRect(e->rect(), QColor(0, 0, 0, alpha));
  786. }
  787. paintRadial(p);
  788. } else if (_generated.isNull()) {
  789. p.fillRect(e->rect(), st::boxBg);
  790. return;
  791. } else {
  792. // Progress of pattern loading.
  793. paintRadial(p);
  794. }
  795. paintTexts(p, ms);
  796. if (_dimmingToggleScheduled) {
  797. crl::on_main(this, [=] {
  798. if (!_dimmingToggleScheduled) {
  799. return;
  800. }
  801. _dimmingToggleScheduled = false;
  802. _dimmingWrap->toggle(_boxDarkMode.current(), anim::type::normal);
  803. });
  804. }
  805. }
  806. void BackgroundPreviewBox::paintImage(Painter &p) {
  807. Expects(!_scaled.isNull());
  808. const auto factor = style::DevicePixelRatio();
  809. const auto size = st::boxWideWidth;
  810. const auto from = QRect(
  811. 0,
  812. (size - height()) / 2 * factor,
  813. size * factor,
  814. height() * factor);
  815. const auto guard = gsl::finally([&] { p.setOpacity(1.); });
  816. const auto fade = _fadeIn.value(1.);
  817. if (fade < 1. && !_fadeOutThumbnail.isNull()) {
  818. p.drawPixmap(rect(), _fadeOutThumbnail, from);
  819. }
  820. const auto &pixmap = (!_blurred.isNull() && _paper.isBlurred())
  821. ? _blurred
  822. : _scaled;
  823. p.setOpacity(fade);
  824. p.drawPixmap(rect(), pixmap, from);
  825. checkBlurAnimationStart();
  826. }
  827. void BackgroundPreviewBox::paintRadial(Painter &p) {
  828. const auto radial = _radial.animating();
  829. const auto radialOpacity = radial ? _radial.opacity() : 0.;
  830. if (!radial) {
  831. return;
  832. }
  833. auto inner = radialRect();
  834. p.setPen(Qt::NoPen);
  835. p.setOpacity(radialOpacity);
  836. p.setBrush(st::radialBg);
  837. {
  838. PainterHighQualityEnabler hq(p);
  839. p.drawEllipse(inner);
  840. }
  841. p.setOpacity(1);
  842. QRect arc(inner.marginsRemoved(QMargins(st::radialLine, st::radialLine, st::radialLine, st::radialLine)));
  843. _radial.draw(p, arc, st::radialLine, st::radialFg);
  844. }
  845. int BackgroundPreviewBox::textsTop() const {
  846. const auto bottom = _blur
  847. ? _blur->y()
  848. : (height() - _dimmingHeight.current());
  849. return bottom
  850. - st::historyPaddingBottom
  851. - (_service ? _service->height() : 0)
  852. - _text1->height()
  853. - (forChannel() ? 0 : _text2->height());
  854. }
  855. QRect BackgroundPreviewBox::radialRect() const {
  856. const auto available = textsTop() - st::historyPaddingBottom;
  857. return QRect(
  858. QPoint(
  859. (width() - st::radialSize.width()) / 2,
  860. (available - st::radialSize.height()) / 2),
  861. st::radialSize);
  862. }
  863. void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) {
  864. const auto heights = _service ? _service->height() : 0;
  865. const auto height1 = _text1->height();
  866. const auto height2 = _text2->height();
  867. auto context = _controller->defaultChatTheme()->preparePaintContext(
  868. _chatStyle.get(),
  869. rect(),
  870. rect(),
  871. _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Layer));
  872. p.translate(0, textsTop());
  873. if (_service) {
  874. _service->draw(p, context);
  875. p.translate(0, heights);
  876. }
  877. context.outbg = _text1->hasOutLayout();
  878. _text1->draw(p, context);
  879. p.translate(0, height1);
  880. if (!forChannel()) {
  881. context.outbg = _text2->hasOutLayout();
  882. _text2->draw(p, context);
  883. p.translate(0, height2);
  884. }
  885. }
  886. void BackgroundPreviewBox::radialAnimationCallback(crl::time now) {
  887. const auto document = _paper.document();
  888. const auto wasAnimating = _radial.animating();
  889. const auto updated = _uploadId
  890. ? _radial.update(_uploadProgress, !_uploadLifetime, now)
  891. : _radial.update(_media->progress(), !document->loading(), now);
  892. if ((wasAnimating || _radial.animating())
  893. && (!anim::Disabled() || updated)) {
  894. update(radialRect());
  895. }
  896. checkLoadedDocument();
  897. }
  898. void BackgroundPreviewBox::setScaledFromThumb() {
  899. if (!_scaled.isNull()) {
  900. return;
  901. }
  902. const auto localThumbnail = _paper.localThumbnail();
  903. const auto thumbnail = localThumbnail
  904. ? localThumbnail
  905. : _media
  906. ? _media->thumbnail()
  907. : nullptr;
  908. if (!thumbnail) {
  909. return;
  910. } else if (_paper.isPattern() && _paper.document() != nullptr) {
  911. return;
  912. }
  913. auto scaled = PrepareScaledFromFull(
  914. thumbnail->original(),
  915. _paper.isPattern(),
  916. _paper.backgroundColors(),
  917. _paper.gradientRotation(),
  918. _paper.patternOpacity(),
  919. _paper.document() ? Images::Option::Blur : Images::Option());
  920. auto blurred = (_paper.document() || _paper.isPattern())
  921. ? QImage()
  922. : PrepareScaledNonPattern(
  923. Ui::PrepareBlurredBackground(thumbnail->original()),
  924. Images::Option(0));
  925. setScaledFromImage(std::move(scaled), std::move(blurred));
  926. }
  927. void BackgroundPreviewBox::setScaledFromImage(
  928. QImage &&image,
  929. QImage &&blurred) {
  930. updateServiceBg({ Ui::CountAverageColor(image) });
  931. if (!_full.isNull()) {
  932. startFadeInFrom(std::move(_scaled));
  933. }
  934. _scaled = Ui::PixmapFromImage(std::move(image));
  935. _blurred = Ui::PixmapFromImage(std::move(blurred));
  936. if (_blur) {
  937. _blur->setDisabled(_paper.document() && _full.isNull());
  938. }
  939. }
  940. void BackgroundPreviewBox::startFadeInFrom(QPixmap previous) {
  941. _fadeOutThumbnail = std::move(previous);
  942. _fadeIn.start([=] { update(); }, 0., 1., st::backgroundCheck.duration);
  943. }
  944. void BackgroundPreviewBox::checkBlurAnimationStart() {
  945. if (_fadeIn.animating()
  946. || _blurred.isNull()
  947. || !_blur
  948. || _paper.isBlurred() == _blur->checked()) {
  949. return;
  950. }
  951. _paper = _paper.withBlurred(_blur->checked());
  952. startFadeInFrom(_paper.isBlurred() ? _scaled : _blurred);
  953. }
  954. void BackgroundPreviewBox::updateServiceBg(const std::vector<QColor> &bg) {
  955. const auto count = int(bg.size());
  956. if (!count) {
  957. return;
  958. }
  959. auto red = 0LL, green = 0LL, blue = 0LL;
  960. for (const auto &color : bg) {
  961. red += color.red();
  962. green += color.green();
  963. blue += color.blue();
  964. }
  965. _serviceBgLifetime = _paletteServiceBg.value(
  966. ) | rpl::start_with_next([=](QColor color) {
  967. _serviceBg = Ui::ThemeAdjustedColor(
  968. color,
  969. QColor(red / count, green / count, blue / count));
  970. _chatStyle->applyAdjustedServiceBg(*_serviceBg);
  971. recreateBlurCheckbox();
  972. });
  973. _service = GenerateServiceItem(
  974. delegate(),
  975. _serviceHistory,
  976. (forGroup()
  977. ? tr::lng_background_other_group(tr::now)
  978. : forChannel()
  979. ? tr::lng_background_other_channel(tr::now)
  980. : (_forPeer
  981. && !_fromMessageId
  982. && !_forPeer->starsPerMessageChecked())
  983. ? tr::lng_background_other_info(
  984. tr::now,
  985. lt_user,
  986. _forPeer->shortName())
  987. : ItemDateText(_text1->data(), false)),
  988. false);
  989. _service->initDimensions();
  990. _service->resizeGetHeight(st::boxWideWidth);
  991. }
  992. void BackgroundPreviewBox::checkLoadedDocument() {
  993. const auto document = _paper.document();
  994. if (!_full.isNull()
  995. || !document
  996. || !_media->loaded(true)
  997. || _generating) {
  998. return;
  999. }
  1000. const auto generateCallback = [=](QImage &&image) {
  1001. if (image.isNull()) {
  1002. return;
  1003. }
  1004. crl::async([
  1005. this,
  1006. image = std::move(image),
  1007. isPattern = _paper.isPattern(),
  1008. background = _paper.backgroundColors(),
  1009. gradientRotation = _paper.gradientRotation(),
  1010. patternOpacity = _paper.patternOpacity(),
  1011. guard = _generating.make_guard()
  1012. ]() mutable {
  1013. auto scaled = PrepareScaledFromFull(
  1014. image,
  1015. isPattern,
  1016. background,
  1017. gradientRotation,
  1018. patternOpacity);
  1019. auto blurred = !isPattern
  1020. ? PrepareScaledNonPattern(
  1021. Ui::PrepareBlurredBackground(image),
  1022. Images::Option(0))
  1023. : QImage();
  1024. crl::on_main(std::move(guard), [
  1025. this,
  1026. image = std::move(image),
  1027. scaled = std::move(scaled),
  1028. blurred = std::move(blurred)
  1029. ]() mutable {
  1030. _full = std::move(image);
  1031. setScaledFromImage(std::move(scaled), std::move(blurred));
  1032. update();
  1033. });
  1034. });
  1035. };
  1036. _generating = Data::ReadBackgroundImageAsync(
  1037. _media.get(),
  1038. Ui::PreprocessBackgroundImage,
  1039. generateCallback);
  1040. }
  1041. bool BackgroundPreviewBox::Start(
  1042. not_null<Window::SessionController*> controller,
  1043. const QString &slug,
  1044. const QMap<QString, QString> &params) {
  1045. if (const auto paper = Data::WallPaper::FromColorsSlug(slug)) {
  1046. controller->show(Box<BackgroundPreviewBox>(
  1047. controller,
  1048. paper->withUrlParams(params)));
  1049. return true;
  1050. }
  1051. if (!IsValidWallPaperSlug(slug)) {
  1052. controller->show(Ui::MakeInformBox(tr::lng_background_bad_link()));
  1053. return false;
  1054. }
  1055. controller->session().api().requestWallPaper(slug, crl::guard(controller, [=](
  1056. const Data::WallPaper &result) {
  1057. controller->show(Box<BackgroundPreviewBox>(
  1058. controller,
  1059. result.withUrlParams(params)));
  1060. }), crl::guard(controller, [=] {
  1061. controller->show(Ui::MakeInformBox(tr::lng_background_bad_link()));
  1062. }));
  1063. return true;
  1064. }
  1065. HistoryView::Context BackgroundPreviewBox::elementContext() {
  1066. return HistoryView::Context::ContactPreview;
  1067. }