tdesktop/Telegram/SourceFiles/boxes/background_preview_box.cpp

1170 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)
* style::DevicePixelRatio();
_generated = Ui::PixmapFromImage((_paper.patternOpacity() >= 0.)
? Ui::GenerateBackgroundImage(
size,
_paper.backgroundColors(),
_paper.gradientRotation())
: BlackImage(size));
_generated.setDevicePixelRatio(style::DevicePixelRatio());
}
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().upload(_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(),
PremiumFeature::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 = style::DevicePixelRatio();
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> &params) {
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;
}