/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/background_preview_box.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "window/themes/window_theme.h" #include "ui/boxes/confirm_box.h" #include "ui/controls/chat_service_checkbox.h" #include "ui/chat/chat_theme.h" #include "ui/chat/chat_style.h" #include "ui/toast/toast.h" #include "ui/image/image.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/continuous_sliders.h" #include "ui/wrap/slide_wrap.h" #include "ui/painter.h" #include "ui/ui_utility.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_item_helpers.h" #include "history/view/history_view_message.h" #include "main/main_session.h" #include "apiwrap.h" #include "data/data_session.h" #include "data/data_user.h" #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_document_resolver.h" #include "data/data_file_origin.h" #include "base/unixtime.h" #include "boxes/background_preview_box.h" #include "window/window_session_controller.h" #include "window/themes/window_themes_embedded.h" #include "settings/settings_common.h" #include "storage/file_upload.h" #include "storage/localimageloader.h" #include "styles/style_chat.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_settings.h" #include #include namespace { constexpr auto kMaxWallPaperSlugLength = 255; constexpr auto kDefaultDimming = 50; [[nodiscard]] bool IsValidWallPaperSlug(const QString &slug) { if (slug.isEmpty() || slug.size() > kMaxWallPaperSlugLength) { return false; } return ranges::none_of(slug, [](QChar ch) { return (ch != '.') && (ch != '_') && (ch != '-') && (ch < '0' || ch > '9') && (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z'); }); } [[nodiscard]] AdminLog::OwnedItem GenerateServiceItem( not_null delegate, not_null history, const QString &text, bool out) { Expects(history->peer->isUser()); const auto flags = MessageFlag::FakeHistoryItem | MessageFlag::HasFromId | (out ? MessageFlag::Outgoing : MessageFlag(0)); const auto item = history->makeMessage( history->owner().nextLocalMessageId(), flags, base::unixtime::now(), PreparedServiceText{ { text } }); return AdminLog::OwnedItem(delegate, item); } [[nodiscard]] AdminLog::OwnedItem GenerateTextItem( not_null delegate, not_null history, const QString &text, bool out) { Expects(history->peer->isUser()); const auto flags = MessageFlag::FakeHistoryItem | MessageFlag::HasFromId | (out ? MessageFlag::Outgoing : MessageFlag(0)); const auto replyTo = FullReplyTo(); const auto viaBotId = UserId(); const auto groupedId = uint64(); const auto item = history->makeMessage( history->nextNonHistoryEntryId(), flags, replyTo, viaBotId, base::unixtime::now(), out ? history->session().userId() : peerToUser(history->peer->id), QString(), TextWithEntities{ text }, MTP_messageMediaEmpty(), HistoryMessageMarkupData(), groupedId); return AdminLog::OwnedItem(delegate, item); } [[nodiscard]] QImage PrepareScaledNonPattern( const QImage &image, Images::Option blur) { const auto size = st::boxWideWidth; const auto width = std::max(image.width(), 1); const auto height = std::max(image.height(), 1); const auto takeWidth = (width > height) ? (width * size / height) : size; const auto takeHeight = (width > height) ? size : (height * size / width); const auto ratio = style::DevicePixelRatio(); return Images::Prepare(image, QSize(takeWidth, takeHeight) * ratio, { .options = Images::Option::TransparentBackground | blur, .outer = { size, size }, }); } [[nodiscard]] QImage PrepareScaledFromFull( const QImage &image, bool isPattern, const std::vector &background, int gradientRotation, float64 patternOpacity, Images::Option blur = Images::Option(0)) { auto result = PrepareScaledNonPattern(image, blur); if (isPattern) { result = Ui::PreparePatternImage( std::move(result), background, gradientRotation, patternOpacity); } return std::move(result).convertToFormat( QImage::Format_ARGB32_Premultiplied); } [[nodiscard]] QImage BlackImage(QSize size) { auto result = QImage(size, QImage::Format_ARGB32_Premultiplied); result.fill(Qt::black); return result; } } // namespace struct BackgroundPreviewBox::OverridenStyle { style::Box box; style::IconButton toggle; style::MediaSlider slider; style::FlatLabel subtitle; }; BackgroundPreviewBox::BackgroundPreviewBox( QWidget*, not_null controller, const Data::WallPaper &paper, BackgroundPreviewArgs args) : SimpleElementDelegate(controller, [=] { update(); }) , _controller(controller) , _forPeer(args.forPeer) , _fromMessageId(args.fromMessageId) , _chatStyle(std::make_unique()) , _serviceHistory(_controller->session().data().history( PeerData::kServiceNotificationsId)) , _service(nullptr) , _text1(GenerateTextItem( delegate(), _serviceHistory, (_forPeer ? tr::lng_background_apply1(tr::now) : tr::lng_background_text1(tr::now)), false)) , _text2(GenerateTextItem( delegate(), _serviceHistory, (_forPeer ? tr::lng_background_apply2(tr::now) : tr::lng_background_text2(tr::now)), true)) , _paper(paper) , _media(_paper.document() ? _paper.document()->createMediaView() : nullptr) , _radial([=](crl::time now) { radialAnimationCallback(now); }) , _appNightMode(Window::Theme::IsNightModeValue()) , _boxDarkMode(_appNightMode.current()) , _dimmingIntensity(std::clamp(paper.patternIntensity(), 0, 100)) , _dimmed(_forPeer && (paper.document() || paper.localThumbnail()) && !paper.isPattern()) { if (_media) { _media->thumbnailWanted(_paper.fileOrigin()); } generateBackground(); _controller->session().downloaderTaskFinished( ) | rpl::start_with_next([=] { update(); }, lifetime()); _appNightMode.changes( ) | rpl::start_with_next([=](bool night) { _boxDarkMode = night; update(); }, lifetime()); _boxDarkMode.changes( ) | rpl::start_with_next([=](bool dark) { applyDarkMode(dark); }, lifetime()); const auto prepare = [=](bool dark, auto pointer) { const auto weak = Ui::MakeWeak(this); crl::async([=] { auto result = std::make_unique(); Window::Theme::PreparePaletteCallback(dark, {})(*result); crl::on_main([=, result = std::move(result)]() mutable { if (const auto strong = weak.data()) { strong->*pointer = std::move(result); strong->paletteReady(); } }); }); }; prepare(false, &BackgroundPreviewBox::_lightPalette); prepare(true, &BackgroundPreviewBox::_darkPalette); } BackgroundPreviewBox::~BackgroundPreviewBox() = default; void BackgroundPreviewBox::applyDarkMode(bool dark) { const auto equals = (dark == Window::Theme::IsNightMode()); const auto &palette = (dark ? _darkPalette : _lightPalette); if (!equals && !palette) { _waitingForPalette = true; return; } _waitingForPalette = false; if (equals) { setStyle(st::defaultBox); _chatStyle->applyCustomPalette(nullptr); _paletteServiceBg = rpl::single( rpl::empty ) | rpl::then( style::PaletteChanged() ) | rpl::map([=] { return st::msgServiceBg->c; }); } else { setStyle(overridenStyle(dark)); _chatStyle->applyCustomPalette(palette.get()); _paletteServiceBg = palette->msgServiceBg()->c; } resetTitle(); rebuildButtons(dark); update(); if (const auto parent = parentWidget()) { parent->update(); } if (_dimmed) { createDimmingSlider(dark); } } void BackgroundPreviewBox::createDimmingSlider(bool dark) { const auto created = !_dimmingWrap; if (created) { _dimmingWrap.create(this, object_ptr(this)); _dimmingContent = _dimmingWrap->entity(); } _dimmingSlider = nullptr; for (const auto &child : _dimmingContent->children()) { if (child->isWidgetType()) { static_cast(child)->hide(); child->deleteLater(); } } const auto equals = (dark == Window::Theme::IsNightMode()); const auto inner = Ui::CreateChild(_dimmingContent); inner->show(); Settings::AddSubsectionTitle( inner, tr::lng_background_dimming(), style::margins(0, st::settingsSectionSkip, 0, 0), equals ? nullptr : dark ? &_dark->subtitle : &_light->subtitle); _dimmingSlider = inner->add( object_ptr( inner, (equals ? st::defaultContinuousSlider : dark ? _dark->slider : _light->slider)), st::localStorageLimitMargin); _dimmingSlider->setValue(_dimmingIntensity / 100.); _dimmingSlider->setAlwaysDisplayMarker(true); _dimmingSlider->resize(st::defaultContinuousSlider.seekSize); const auto handle = [=](float64 value) { const auto intensity = std::clamp( int(base::SafeRound(value * 100)), 0, 100); _paper = _paper.withPatternIntensity(intensity); _dimmingIntensity = intensity; update(); }; _dimmingSlider->setChangeProgressCallback(handle); _dimmingSlider->setChangeFinishedCallback(handle); inner->resizeToWidth(st::boxWideWidth); Ui::SendPendingMoveResizeEvents(inner); inner->move(0, 0); _dimmingContent->resize(inner->size()); _dimmingContent->paintRequest( ) | rpl::start_with_next([=](QRect clip) { auto p = QPainter(_dimmingContent); const auto palette = (dark ? _darkPalette : _lightPalette).get(); p.fillRect(clip, equals ? st::boxBg : palette->boxBg()); }, _dimmingContent->lifetime()); _dimmingToggleScheduled = true; if (created) { rpl::combine( heightValue(), _dimmingWrap->heightValue(), rpl::mappers::_1 - rpl::mappers::_2 ) | rpl::start_with_next([=](int top) { _dimmingWrap->move(0, top); }, _dimmingWrap->lifetime()); _dimmingWrap->toggle(dark, anim::type::instant); _dimmingHeight = _dimmingWrap->heightValue(); _dimmingHeight.changes() | rpl::start_with_next([=] { update(); }, _dimmingWrap->lifetime()); } } void BackgroundPreviewBox::paletteReady() { if (_waitingForPalette) { applyDarkMode(_boxDarkMode.current()); } } const style::Box &BackgroundPreviewBox::overridenStyle(bool dark) { auto &st = dark ? _dark : _light; if (!st) { st = std::make_unique(prepareOverridenStyle(dark)); } return st->box; } auto BackgroundPreviewBox::prepareOverridenStyle(bool dark) -> OverridenStyle { const auto p = (dark ? _darkPalette : _lightPalette).get(); Assert(p != nullptr); const auto &toggle = dark ? st::backgroundSwitchToLight : st::backgroundSwitchToDark; auto result = OverridenStyle{ .box = st::defaultBox, .toggle = toggle, .slider = st::defaultContinuousSlider, .subtitle = st::settingsSubsectionTitle, }; result.box.button.textFg = p->lightButtonFg(); result.box.button.textFgOver = p->lightButtonFgOver(); result.box.button.numbersTextFg = p->lightButtonFg(); result.box.button.numbersTextFgOver = p->lightButtonFgOver(); result.box.button.textBg = p->lightButtonBg(); result.box.button.textBgOver = p->lightButtonBgOver(); result.box.button.ripple.color = p->lightButtonBgRipple(); result.box.title.textFg = p->boxTitleFg(); result.box.bg = p->boxBg(); result.box.titleAdditionalFg = p->boxTitleAdditionalFg(); result.toggle.ripple.color = p->windowBgOver(); result.toggle.icon = toggle.icon.withPalette(*p); result.toggle.iconOver = toggle.iconOver.withPalette(*p); result.slider.activeFg = p->mediaPlayerActiveFg(); result.slider.inactiveFg = p->mediaPlayerInactiveFg(); result.slider.activeFgOver = p->mediaPlayerActiveFg(); result.slider.inactiveFgOver = p->mediaPlayerInactiveFg(); result.slider.activeFgDisabled = p->mediaPlayerInactiveFg(); result.slider.inactiveFgDisabled = p->windowBg(); result.slider.receivedTillFg = p->mediaPlayerInactiveFg(); result.subtitle.textFg = p->windowActiveTextFg(); return result; } void BackgroundPreviewBox::generateBackground() { if (_paper.backgroundColors().empty()) { return; } const auto size = QSize(st::boxWideWidth, st::boxWideWidth) * cIntRetinaFactor(); _generated = Ui::PixmapFromImage((_paper.patternOpacity() >= 0.) ? Ui::GenerateBackgroundImage( size, _paper.backgroundColors(), _paper.gradientRotation()) : BlackImage(size)); _generated.setDevicePixelRatio(cRetinaFactor()); } not_null BackgroundPreviewBox::delegate() { return static_cast(this); } void BackgroundPreviewBox::resetTitle() { setTitle(tr::lng_background_header()); } void BackgroundPreviewBox::rebuildButtons(bool dark) { clearButtons(); addButton(_forPeer ? tr::lng_background_apply_button() : tr::lng_background_apply(), [=] { apply(); }); addButton(tr::lng_cancel(), [=] { closeBox(); }); if (!_forPeer && _paper.hasShareUrl()) { addLeftButton(tr::lng_background_share(), [=] { share(); }); } const auto equals = (dark == Window::Theme::IsNightMode()); auto toggle = object_ptr(this, equals ? (dark ? st::backgroundSwitchToLight : st::backgroundSwitchToDark) : dark ? _dark->toggle : _light->toggle); toggle->setClickedCallback([=] { _boxDarkMode = !_boxDarkMode.current(); }); addTopButton(std::move(toggle)); } void BackgroundPreviewBox::prepare() { applyDarkMode(Window::Theme::IsNightMode()); _paper.loadDocument(); if (const auto document = _paper.document()) { if (document->loading()) { _radial.start(_media->progress()); } } updateServiceBg(_paper.backgroundColors()); setScaledFromThumb(); checkLoadedDocument(); _text1->setDisplayDate(false); _text1->initDimensions(); _text1->resizeGetHeight(st::boxWideWidth); _text2->initDimensions(); _text2->resizeGetHeight(st::boxWideWidth); setDimensions(st::boxWideWidth, st::boxWideWidth); } void BackgroundPreviewBox::recreateBlurCheckbox() { const auto document = _paper.document(); if (_paper.isPattern() || (!_paper.localThumbnail() && (!document || !document->hasThumbnail()))) { return; } const auto blurred = _blur ? _blur->checked() : _paper.isBlurred(); _blur = Ui::MakeChatServiceCheckbox( this, tr::lng_background_blur(tr::now), st::backgroundCheckbox, st::backgroundCheck, blurred, [=] { return _serviceBg.value_or(QColor(255, 255, 255, 0)); }); _blur->show(); rpl::combine( sizeValue(), _blur->sizeValue(), _dimmingHeight.value() ) | rpl::start_with_next([=](QSize outer, QSize inner, int dimming) { const auto bottom = st::historyPaddingBottom; _blur->move( (outer.width() - inner.width()) / 2, outer.height() - dimming - bottom - inner.height()); }, _blur->lifetime()); _blur->checkedChanges( ) | rpl::start_with_next([=](bool checked) { checkBlurAnimationStart(); update(); }, _blur->lifetime()); _blur->setDisabled(_paper.document() && _full.isNull()); } void BackgroundPreviewBox::apply() { if (_forPeer) { applyForPeer(); } else { applyForEveryone(); } } void BackgroundPreviewBox::uploadForPeer() { Expects(_forPeer != nullptr); if (_uploadId) { return; } const auto session = &_controller->session(); const auto ready = Window::Theme::PrepareWallPaper( session->mainDcId(), _paper.localThumbnail()->original()); const auto documentId = ready.id; _uploadId = FullMsgId( session->userPeerId(), session->data().nextLocalMessageId()); session->uploader().uploadMedia(_uploadId, ready); if (_uploadLifetime) { return; } const auto document = session->data().document(documentId); document->uploadingData = std::make_unique( document->size); session->uploader().documentProgress( ) | rpl::start_with_next([=](const FullMsgId &fullId) { if (fullId != _uploadId) { return; } _uploadProgress = document->uploading() ? ((document->uploadingData->offset * 100) / document->uploadingData->size) : 0.; update(radialRect()); }, _uploadLifetime); session->uploader().documentReady( ) | rpl::start_with_next([=](const Storage::UploadedMedia &data) { if (data.fullId != _uploadId) { return; } _uploadProgress = 1.; _uploadLifetime.destroy(); update(radialRect()); session->api().request(MTPaccount_UploadWallPaper( MTP_flags(MTPaccount_UploadWallPaper::Flag::f_for_chat), data.info.file, MTP_string("image/jpeg"), _paper.mtpSettings() )).done([=](const MTPWallPaper &result) { result.match([&](const MTPDwallPaper &data) { session->data().documentConvert( session->data().document(documentId), data.vdocument()); }, [&](const MTPDwallPaperNoFile &data) { LOG(("API Error: " "Got wallPaperNoFile after account.UploadWallPaper.")); }); if (const auto paper = Data::WallPaper::Create(session, result)) { setExistingForPeer(*paper); } }).send(); }, _uploadLifetime); _uploadProgress = 0.; _radial.start(_uploadProgress); } void BackgroundPreviewBox::setExistingForPeer(const Data::WallPaper &paper) { Expects(_forPeer != nullptr); if (const auto already = _forPeer->wallPaper()) { if (already->equals(paper)) { _controller->finishChatThemeEdit(_forPeer); return; } } const auto api = &_controller->session().api(); using Flag = MTPmessages_SetChatWallPaper::Flag; api->request(MTPmessages_SetChatWallPaper( MTP_flags((_fromMessageId ? Flag::f_id : Flag()) | (_fromMessageId ? Flag() : Flag::f_wallpaper) | Flag::f_settings), _forPeer->input, paper.mtpInput(&_controller->session()), paper.mtpSettings(), MTP_int(_fromMessageId.msg) )).done([=](const MTPUpdates &result) { api->applyUpdates(result); }).send(); _forPeer->setWallPaper(paper); _controller->finishChatThemeEdit(_forPeer); } void BackgroundPreviewBox::applyForPeer() { Expects(_forPeer != nullptr); if (Data::IsCustomWallPaper(_paper)) { uploadForPeer(); } else { setExistingForPeer(_paper); } } void BackgroundPreviewBox::applyForEveryone() { const auto install = (_paper.id() != Window::Theme::Background()->id()) && Data::IsCloudWallPaper(_paper); _controller->content()->setChatBackground(_paper, std::move(_full)); if (install) { _controller->session().api().request(MTPaccount_InstallWallPaper( _paper.mtpInput(&_controller->session()), _paper.mtpSettings() )).send(); } closeBox(); } void BackgroundPreviewBox::share() { QGuiApplication::clipboard()->setText( _paper.shareUrl(&_controller->session())); showToast(tr::lng_background_link_copied(tr::now)); } void BackgroundPreviewBox::paintEvent(QPaintEvent *e) { Painter p(this); const auto ms = crl::now(); if (_scaled.isNull()) { setScaledFromThumb(); } if (!_generated.isNull() && (_scaled.isNull() || (_fadeOutThumbnail.isNull() && _fadeIn.animating()))) { p.drawPixmap(0, 0, _generated); } if (!_scaled.isNull()) { paintImage(p); const auto dimming = (_dimmed && _boxDarkMode.current()) ? _dimmingIntensity : 0; if (dimming > 0) { const auto alpha = 255 * dimming / 100; p.fillRect(e->rect(), QColor(0, 0, 0, alpha)); } paintRadial(p); } else if (_generated.isNull()) { p.fillRect(e->rect(), st::boxBg); return; } else { // Progress of pattern loading. paintRadial(p); } paintTexts(p, ms); if (_dimmingToggleScheduled) { crl::on_main(this, [=] { if (!_dimmingToggleScheduled) { return; } _dimmingToggleScheduled = false; _dimmingWrap->toggle(_boxDarkMode.current(), anim::type::normal); }); } } void BackgroundPreviewBox::paintImage(Painter &p) { Expects(!_scaled.isNull()); const auto factor = cIntRetinaFactor(); const auto size = st::boxWideWidth; const auto from = QRect( 0, (size - height()) / 2 * factor, size * factor, height() * factor); const auto guard = gsl::finally([&] { p.setOpacity(1.); }); const auto fade = _fadeIn.value(1.); if (fade < 1. && !_fadeOutThumbnail.isNull()) { p.drawPixmap(rect(), _fadeOutThumbnail, from); } const auto &pixmap = (!_blurred.isNull() && _paper.isBlurred()) ? _blurred : _scaled; p.setOpacity(fade); p.drawPixmap(rect(), pixmap, from); checkBlurAnimationStart(); } void BackgroundPreviewBox::paintRadial(Painter &p) { const auto radial = _radial.animating(); const auto radialOpacity = radial ? _radial.opacity() : 0.; if (!radial) { return; } auto inner = radialRect(); p.setPen(Qt::NoPen); p.setOpacity(radialOpacity); p.setBrush(st::radialBg); { PainterHighQualityEnabler hq(p); p.drawEllipse(inner); } p.setOpacity(1); QRect arc(inner.marginsRemoved(QMargins(st::radialLine, st::radialLine, st::radialLine, st::radialLine))); _radial.draw(p, arc, st::radialLine, st::radialFg); } int BackgroundPreviewBox::textsTop() const { const auto bottom = _blur ? _blur->y() : (height() - _dimmingHeight.current()); return bottom - st::historyPaddingBottom - (_service ? _service->height() : 0) - _text1->height() - _text2->height(); } QRect BackgroundPreviewBox::radialRect() const { const auto available = textsTop() - st::historyPaddingBottom; return QRect( QPoint( (width() - st::radialSize.width()) / 2, (available - st::radialSize.height()) / 2), st::radialSize); } void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) { const auto heights = _service ? _service->height() : 0; const auto height1 = _text1->height(); const auto height2 = _text2->height(); auto context = _controller->defaultChatTheme()->preparePaintContext( _chatStyle.get(), rect(), rect(), _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Layer)); p.translate(0, textsTop()); if (_service) { _service->draw(p, context); p.translate(0, heights); } context.outbg = _text1->hasOutLayout(); _text1->draw(p, context); p.translate(0, height1); context.outbg = _text2->hasOutLayout(); _text2->draw(p, context); p.translate(0, height2); } void BackgroundPreviewBox::radialAnimationCallback(crl::time now) { const auto document = _paper.document(); const auto wasAnimating = _radial.animating(); const auto updated = _uploadId ? _radial.update(_uploadProgress, !_uploadLifetime, now) : _radial.update(_media->progress(), !document->loading(), now); if ((wasAnimating || _radial.animating()) && (!anim::Disabled() || updated)) { update(radialRect()); } checkLoadedDocument(); } void BackgroundPreviewBox::setScaledFromThumb() { if (!_scaled.isNull()) { return; } const auto localThumbnail = _paper.localThumbnail(); const auto thumbnail = localThumbnail ? localThumbnail : _media ? _media->thumbnail() : nullptr; if (!thumbnail) { return; } else if (_paper.isPattern() && _paper.document() != nullptr) { return; } auto scaled = PrepareScaledFromFull( thumbnail->original(), _paper.isPattern(), _paper.backgroundColors(), _paper.gradientRotation(), _paper.patternOpacity(), _paper.document() ? Images::Option::Blur : Images::Option()); auto blurred = (_paper.document() || _paper.isPattern()) ? QImage() : PrepareScaledNonPattern( Ui::PrepareBlurredBackground(thumbnail->original()), Images::Option(0)); setScaledFromImage(std::move(scaled), std::move(blurred)); } void BackgroundPreviewBox::setScaledFromImage( QImage &&image, QImage &&blurred) { updateServiceBg({ Ui::CountAverageColor(image) }); if (!_full.isNull()) { startFadeInFrom(std::move(_scaled)); } _scaled = Ui::PixmapFromImage(std::move(image)); _blurred = Ui::PixmapFromImage(std::move(blurred)); if (_blur) { _blur->setDisabled(_paper.document() && _full.isNull()); } } void BackgroundPreviewBox::startFadeInFrom(QPixmap previous) { _fadeOutThumbnail = std::move(previous); _fadeIn.start([=] { update(); }, 0., 1., st::backgroundCheck.duration); } void BackgroundPreviewBox::checkBlurAnimationStart() { if (_fadeIn.animating() || _blurred.isNull() || !_blur || _paper.isBlurred() == _blur->checked()) { return; } _paper = _paper.withBlurred(_blur->checked()); startFadeInFrom(_paper.isBlurred() ? _scaled : _blurred); } void BackgroundPreviewBox::updateServiceBg(const std::vector &bg) { const auto count = int(bg.size()); if (!count) { return; } auto red = 0LL, green = 0LL, blue = 0LL; for (const auto &color : bg) { red += color.red(); green += color.green(); blue += color.blue(); } _serviceBgLifetime = _paletteServiceBg.value( ) | rpl::start_with_next([=](QColor color) { _serviceBg = Ui::ThemeAdjustedColor( color, QColor(red / count, green / count, blue / count)); _chatStyle->applyAdjustedServiceBg(*_serviceBg); recreateBlurCheckbox(); }); _service = GenerateServiceItem( delegate(), _serviceHistory, ((_forPeer && !_fromMessageId) ? tr::lng_background_other_info( tr::now, lt_user, _forPeer->shortName()) : ItemDateText(_text1->data(), false)), false); _service->initDimensions(); _service->resizeGetHeight(st::boxWideWidth); } void BackgroundPreviewBox::checkLoadedDocument() { const auto document = _paper.document(); if (!_full.isNull() || !document || !_media->loaded(true) || _generating) { return; } const auto generateCallback = [=](QImage &&image) { if (image.isNull()) { return; } crl::async([ this, image = std::move(image), isPattern = _paper.isPattern(), background = _paper.backgroundColors(), gradientRotation = _paper.gradientRotation(), patternOpacity = _paper.patternOpacity(), guard = _generating.make_guard() ]() mutable { auto scaled = PrepareScaledFromFull( image, isPattern, background, gradientRotation, patternOpacity); auto blurred = !isPattern ? PrepareScaledNonPattern( Ui::PrepareBlurredBackground(image), Images::Option(0)) : QImage(); crl::on_main(std::move(guard), [ this, image = std::move(image), scaled = std::move(scaled), blurred = std::move(blurred) ]() mutable { _full = std::move(image); setScaledFromImage(std::move(scaled), std::move(blurred)); update(); }); }); }; _generating = Data::ReadBackgroundImageAsync( _media.get(), Ui::PreprocessBackgroundImage, generateCallback); } bool BackgroundPreviewBox::Start( not_null controller, const QString &slug, const QMap ¶ms) { if (const auto paper = Data::WallPaper::FromColorsSlug(slug)) { controller->show(Box( controller, paper->withUrlParams(params))); return true; } if (!IsValidWallPaperSlug(slug)) { controller->show(Ui::MakeInformBox(tr::lng_background_bad_link())); return false; } controller->session().api().requestWallPaper(slug, crl::guard(controller, [=]( const Data::WallPaper &result) { controller->show(Box( controller, result.withUrlParams(params))); }), crl::guard(controller, [=] { controller->show(Ui::MakeInformBox(tr::lng_background_bad_link())); })); return true; } HistoryView::Context BackgroundPreviewBox::elementContext() { return HistoryView::Context::ContactPreview; }