1169 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1169 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| 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 "base/unixtime.h"
 | |
| #include "boxes/peers/edit_peer_color_box.h"
 | |
| #include "boxes/premium_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/boxes/boost_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/fade_wrap.h"
 | |
| #include "ui/wrap/slide_wrap.h"
 | |
| #include "ui/painter.h"
 | |
| #include "ui/vertical_list.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 "data/data_peer_values.h"
 | |
| #include "data/data_premium_limits.h"
 | |
| #include "settings/settings_premium.h"
 | |
| #include "storage/file_upload.h"
 | |
| #include "storage/localimageloader.h"
 | |
| #include "window/window_session_controller.h"
 | |
| #include "window/themes/window_themes_embedded.h"
 | |
| #include "styles/style_chat.h"
 | |
| #include "styles/style_layers.h"
 | |
| #include "styles/style_boxes.h"
 | |
| 
 | |
| #include <QtGui/QClipboard>
 | |
| #include <QtGui/QGuiApplication>
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| constexpr auto kMaxWallPaperSlugLength = 255;
 | |
| 
 | |
| [[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<HistoryView::ElementDelegate*> delegate,
 | |
| 		not_null<History*> 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({
 | |
| 		.id = history->owner().nextLocalMessageId(),
 | |
| 		.flags = flags,
 | |
| 		.date = base::unixtime::now(),
 | |
| 	}, PreparedServiceText{ { text } });
 | |
| 	return AdminLog::OwnedItem(delegate, item);
 | |
| }
 | |
| 
 | |
| [[nodiscard]] AdminLog::OwnedItem GenerateTextItem(
 | |
| 		not_null<HistoryView::ElementDelegate*> delegate,
 | |
| 		not_null<History*> history,
 | |
| 		const QString &text,
 | |
| 		bool out) {
 | |
| 	Expects(history->peer->isUser());
 | |
| 
 | |
| 	const auto item = history->makeMessage({
 | |
| 		.id = history->nextNonHistoryEntryId(),
 | |
| 		.flags = (MessageFlag::FakeHistoryItem
 | |
| 			| MessageFlag::HasFromId
 | |
| 			| (out ? MessageFlag::Outgoing : MessageFlag(0))),
 | |
| 		.from = (out
 | |
| 			? history->session().userId()
 | |
| 			: peerToUser(history->peer->id)),
 | |
| 		.date = base::unixtime::now(),
 | |
| 	}, TextWithEntities{ text }, MTP_messageMediaEmpty());
 | |
| 	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<QColor> &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;
 | |
| }
 | |
| 
 | |
| [[nodiscard]] Data::WallPaper Resolve(
 | |
| 		not_null<Main::Session*> session,
 | |
| 		const Data::WallPaper &paper,
 | |
| 		bool dark) {
 | |
| 	if (paper.emojiId().isEmpty()) {
 | |
| 		return paper;
 | |
| 	}
 | |
| 	const auto &themes = session->data().cloudThemes();
 | |
| 	if (const auto theme = themes.themeForEmoji(paper.emojiId())) {
 | |
| 		using Type = Data::CloudThemeType;
 | |
| 		const auto type = dark ? Type::Dark : Type::Light;
 | |
| 		const auto i = theme->settings.find(type);
 | |
| 		if (i != end(theme->settings) && i->second.paper) {
 | |
| 			return *i->second.paper;
 | |
| 		}
 | |
| 	}
 | |
| 	return paper;
 | |
| }
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| struct BackgroundPreviewBox::OverridenStyle {
 | |
| 	style::Box box;
 | |
| 	style::IconButton toggle;
 | |
| 	style::MediaSlider slider;
 | |
| 	style::FlatLabel subtitle;
 | |
| };
 | |
| 
 | |
| BackgroundPreviewBox::BackgroundPreviewBox(
 | |
| 	QWidget*,
 | |
| 	not_null<Window::SessionController*> controller,
 | |
| 	const Data::WallPaper &paper,
 | |
| 	BackgroundPreviewArgs args)
 | |
| : SimpleElementDelegate(controller, [=] { update(); })
 | |
| , _controller(controller)
 | |
| , _forPeer(args.forPeer)
 | |
| , _fromMessageId(args.fromMessageId)
 | |
| , _chatStyle(std::make_unique<Ui::ChatStyle>(
 | |
| 	controller->session().colorIndicesValue()))
 | |
| , _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))
 | |
| , _paperEmojiId(paper.emojiId())
 | |
| , _paper(
 | |
| 	Resolve(&controller->session(), paper, Window::Theme::IsNightMode()))
 | |
| , _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<style::palette>();
 | |
| 			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::recreate(bool dark) {
 | |
| 	_paper = Resolve(
 | |
| 		&_controller->session(),
 | |
| 		Data::WallPaper::FromEmojiId(_paperEmojiId),
 | |
| 		dark);
 | |
| 	_media = _paper.document()
 | |
| 		? _paper.document()->createMediaView()
 | |
| 		: nullptr;
 | |
| 	if (_media) {
 | |
| 		_media->thumbnailWanted(_paper.fileOrigin());
 | |
| 	}
 | |
| 	_full = QImage();
 | |
| 	_generated = _scaled = _blurred = _fadeOutThumbnail = QPixmap();
 | |
| 	_generating = {};
 | |
| 	generateBackground();
 | |
| 	_paper.loadDocument();
 | |
| 	if (const auto document = _paper.document()) {
 | |
| 		if (document->loading()) {
 | |
| 			_radial.start(_media->progress());
 | |
| 		}
 | |
| 	}
 | |
| 	checkLoadedDocument();
 | |
| 	updateServiceBg(_paper.backgroundColors());
 | |
| 	update();
 | |
| }
 | |
| 
 | |
| void BackgroundPreviewBox::applyDarkMode(bool dark) {
 | |
| 	if (!_paperEmojiId.isEmpty()) {
 | |
| 		recreate(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<Ui::RpWidget>(this));
 | |
| 		_dimmingContent = _dimmingWrap->entity();
 | |
| 	}
 | |
| 	_dimmingSlider = nullptr;
 | |
| 	for (const auto &child : _dimmingContent->children()) {
 | |
| 		if (child->isWidgetType()) {
 | |
| 			static_cast<QWidget*>(child)->hide();
 | |
| 			child->deleteLater();
 | |
| 		}
 | |
| 	}
 | |
| 	const auto equals = (dark == Window::Theme::IsNightMode());
 | |
| 	const auto inner = Ui::CreateChild<Ui::VerticalLayout>(_dimmingContent);
 | |
| 	inner->show();
 | |
| 	Ui::AddSubsectionTitle(
 | |
| 		inner,
 | |
| 		tr::lng_background_dimming(),
 | |
| 		style::margins(0, st::defaultVerticalListSkip, 0, 0),
 | |
| 		equals ? nullptr : dark ? &_dark->subtitle : &_light->subtitle);
 | |
| 	_dimmingSlider = inner->add(
 | |
| 		object_ptr<Ui::MediaSlider>(
 | |
| 			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<OverridenStyle>(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::defaultSubsectionTitle,
 | |
| 	};
 | |
| 	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;
 | |
| }
 | |
| 
 | |
| bool BackgroundPreviewBox::forChannel() const {
 | |
| 	return _forPeer && _forPeer->isChannel();
 | |
| }
 | |
| 
 | |
| bool BackgroundPreviewBox::forGroup() const {
 | |
| 	return forChannel() && _forPeer->isMegagroup();
 | |
| }
 | |
| 
 | |
| 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<HistoryView::ElementDelegate*> BackgroundPreviewBox::delegate() {
 | |
| 	return static_cast<HistoryView::ElementDelegate*>(this);
 | |
| }
 | |
| 
 | |
| void BackgroundPreviewBox::resetTitle() {
 | |
| 	setTitle(tr::lng_background_header());
 | |
| }
 | |
| 
 | |
| void BackgroundPreviewBox::rebuildButtons(bool dark) {
 | |
| 	clearButtons();
 | |
| 	addButton(forGroup()
 | |
| 		? tr::lng_background_apply_group()
 | |
| 		: forChannel()
 | |
| 		? tr::lng_background_apply_channel()
 | |
| 		: _forPeer
 | |
| 		? tr::lng_background_apply_button()
 | |
| 		: tr::lng_settings_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<Ui::IconButton>(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());
 | |
| 
 | |
| 	if (_forBothOverlay) {
 | |
| 		_forBothOverlay->raise();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void BackgroundPreviewBox::apply() {
 | |
| 	if (_forPeer) {
 | |
| 		applyForPeer();
 | |
| 	} else {
 | |
| 		applyForEveryone();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void BackgroundPreviewBox::uploadForPeer(bool both) {
 | |
| 	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<Data::UploadState>(
 | |
| 		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, both);
 | |
| 			}
 | |
| 		}).send();
 | |
| 	}, _uploadLifetime);
 | |
| 
 | |
| 	_uploadProgress = 0.;
 | |
| 	_radial.start(_uploadProgress);
 | |
| }
 | |
| 
 | |
| void BackgroundPreviewBox::setExistingForPeer(
 | |
| 		const Data::WallPaper &paper,
 | |
| 		bool both) {
 | |
| 	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)
 | |
| 			| (both ? Flag::f_for_both : Flag())
 | |
| 			| 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::checkLevelForChannel() {
 | |
| 	Expects(forChannel());
 | |
| 
 | |
| 	const auto show = _controller->uiShow();
 | |
| 	_forPeerLevelCheck = true;
 | |
| 	const auto weak = Ui::MakeWeak(this);
 | |
| 	CheckBoostLevel(show, _forPeer, [=](int level) {
 | |
| 		if (!weak) {
 | |
| 			return std::optional<Ui::AskBoostReason>();
 | |
| 		}
 | |
| 		const auto limits = Data::LevelLimits(&_forPeer->session());
 | |
| 		const auto required = _paperEmojiId.isEmpty()
 | |
| 			? limits.channelCustomWallpaperLevelMin()
 | |
| 			: limits.channelWallpaperLevelMin();
 | |
| 		if (level >= required) {
 | |
| 			applyForPeer(false);
 | |
| 			return std::optional<Ui::AskBoostReason>();
 | |
| 		}
 | |
| 		return std::make_optional(Ui::AskBoostReason{
 | |
| 			Ui::AskBoostWallpaper{ required, _forPeer->isMegagroup()}
 | |
| 		});
 | |
| 	}, [=] { _forPeerLevelCheck = false; });
 | |
| }
 | |
| 
 | |
| void BackgroundPreviewBox::applyForPeer() {
 | |
| 	Expects(_forPeer != nullptr);
 | |
| 
 | |
| 	if (!Data::IsCustomWallPaper(_paper)) {
 | |
| 		if (const auto already = _forPeer->wallPaper()) {
 | |
| 			if (already->equals(_paper)) {
 | |
| 				_controller->finishChatThemeEdit(_forPeer);
 | |
| 				return;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (forChannel()) {
 | |
| 		checkLevelForChannel();
 | |
| 		return;
 | |
| 	} else if (_fromMessageId || !_forPeer->session().premiumPossible()) {
 | |
| 		applyForPeer(false);
 | |
| 		return;
 | |
| 	} else if (_forBothOverlay) {
 | |
| 		return;
 | |
| 	}
 | |
| 	const auto size = this->size() * style::DevicePixelRatio();
 | |
| 	const auto bg = Images::DitherImage(
 | |
| 		Images::BlurLargeImage(
 | |
| 			Ui::GrabWidgetToImage(this).scaled(
 | |
| 				size / style::ConvertScale(4),
 | |
| 				Qt::IgnoreAspectRatio,
 | |
| 				Qt::SmoothTransformation),
 | |
| 			24).scaled(
 | |
| 				size,
 | |
| 				Qt::IgnoreAspectRatio,
 | |
| 				Qt::SmoothTransformation));
 | |
| 
 | |
| 	_forBothOverlay = std::make_unique<Ui::FadeWrap<>>(
 | |
| 		this,
 | |
| 		object_ptr<Ui::RpWidget>(this));
 | |
| 	const auto overlay = _forBothOverlay->entity();
 | |
| 
 | |
| 	sizeValue() | rpl::start_with_next([=](QSize size) {
 | |
| 		_forBothOverlay->setGeometry({ QPoint(), size });
 | |
| 		overlay->setGeometry({ QPoint(), size });
 | |
| 	}, _forBothOverlay->lifetime());
 | |
| 
 | |
| 	overlay->paintRequest(
 | |
| 	) | rpl::start_with_next([=](QRect clip) {
 | |
| 		auto p = QPainter(overlay);
 | |
| 		p.drawImage(0, 0, bg);
 | |
| 		p.fillRect(clip, QColor(0, 0, 0, 64));
 | |
| 	}, overlay->lifetime());
 | |
| 
 | |
| 	using namespace Ui;
 | |
| 	const auto forMe = CreateChild<RoundButton>(
 | |
| 		overlay,
 | |
| 		tr::lng_background_apply_me(),
 | |
| 		st::backgroundConfirm);
 | |
| 	forMe->setClickedCallback([=] {
 | |
| 		applyForPeer(false);
 | |
| 	});
 | |
| 	using namespace rpl::mappers;
 | |
| 	const auto forBoth = ::Settings::CreateLockedButton(
 | |
| 		overlay,
 | |
| 		tr::lng_background_apply_both(
 | |
| 			lt_user,
 | |
| 			rpl::single(_forPeer->shortName())),
 | |
| 		st::backgroundConfirm,
 | |
| 		Data::AmPremiumValue(&_forPeer->session()) | rpl::map(!_1));
 | |
| 	forBoth->setClickedCallback([=] {
 | |
| 		if (_forPeer->session().premium()) {
 | |
| 			applyForPeer(true);
 | |
| 		} else {
 | |
| 			ShowPremiumPreviewBox(
 | |
| 				_controller->uiShow(),
 | |
| 				PremiumPreview::Wallpapers);
 | |
| 		}
 | |
| 	});
 | |
| 	const auto cancel = CreateChild<RoundButton>(
 | |
| 		overlay,
 | |
| 		tr::lng_cancel(),
 | |
| 		st::backgroundConfirmCancel);
 | |
| 	cancel->setClickedCallback([=] {
 | |
| 		const auto raw = _forBothOverlay.release();
 | |
| 		raw->shownValue() | rpl::filter(
 | |
| 			!rpl::mappers::_1
 | |
| 		) | rpl::take(1) | rpl::start_with_next(crl::guard(raw, [=] {
 | |
| 			delete raw;
 | |
| 		}), raw->lifetime());
 | |
| 		raw->toggle(false, anim::type::normal);
 | |
| 	});
 | |
| 	forMe->setTextTransform(RoundButton::TextTransform::NoTransform);
 | |
| 	forBoth->setTextTransform(RoundButton::TextTransform::NoTransform);
 | |
| 	cancel->setTextTransform(RoundButton::TextTransform::NoTransform);
 | |
| 
 | |
| 	overlay->sizeValue(
 | |
| 	) | rpl::start_with_next([=](QSize size) {
 | |
| 		const auto padding = st::backgroundConfirmPadding;
 | |
| 		const auto width = size.width()
 | |
| 			- padding.left()
 | |
| 			- padding.right();
 | |
| 		const auto height = cancel->height();
 | |
| 		auto top = size.height() - padding.bottom() - height;
 | |
| 		cancel->setGeometry(padding.left(), top, width, height);
 | |
| 		top -= height + padding.top();
 | |
| 		forBoth->setGeometry(padding.left(), top, width, height);
 | |
| 		top -= height + padding.top();
 | |
| 		forMe->setGeometry(padding.left(), top, width, height);
 | |
| 	}, _forBothOverlay->lifetime());
 | |
| 
 | |
| 	_forBothOverlay->hide(anim::type::instant);
 | |
| 	_forBothOverlay->show(anim::type::normal);
 | |
| }
 | |
| 
 | |
| void BackgroundPreviewBox::applyForPeer(bool both) {
 | |
| 	using namespace Data;
 | |
| 	if (forChannel() && !_paperEmojiId.isEmpty()) {
 | |
| 		setExistingForPeer(WallPaper::FromEmojiId(_paperEmojiId), both);
 | |
| 	} else if (IsCustomWallPaper(_paper)) {
 | |
| 		uploadForPeer(both);
 | |
| 	} else {
 | |
| 		setExistingForPeer(_paper, both);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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()
 | |
| 		- (forChannel() ? _text2->height() : 0);
 | |
| }
 | |
| 
 | |
| 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);
 | |
| 	if (!forChannel()) {
 | |
| 		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<QColor> &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,
 | |
| 		(forGroup()
 | |
| 			? tr::lng_background_other_group(tr::now)
 | |
| 			: forChannel()
 | |
| 			? tr::lng_background_other_channel(tr::now)
 | |
| 			: (_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<Window::SessionController*> controller,
 | |
| 		const QString &slug,
 | |
| 		const QMap<QString, QString> ¶ms) {
 | |
| 	if (const auto paper = Data::WallPaper::FromColorsSlug(slug)) {
 | |
| 		controller->show(Box<BackgroundPreviewBox>(
 | |
| 			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<BackgroundPreviewBox>(
 | |
| 			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;
 | |
| }
 | 
