Apply cloud themes.

This commit is contained in:
John Preston 2019-09-03 21:04:38 +03:00
parent ac8f924909
commit 4929de2bfb
12 changed files with 280 additions and 152 deletions

View file

@ -8,11 +8,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_cloud_themes.h" #include "data/data_cloud_themes.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_document.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "apiwrap.h" #include "apiwrap.h"
namespace Data { namespace Data {
CloudTheme CloudTheme::Parse(
not_null<Main::Session*> session,
const MTPDtheme &data) {
const auto document = data.vdocument();
return {
data.vid().v,
data.vaccess_hash().v,
qs(data.vslug()),
qs(data.vtitle()),
(document
? session->data().processDocument(*document)->id
: DocumentId(0)),
data.is_creator() ? session->userId() : UserId(0)
};
}
QString CloudThemes::Format() { QString CloudThemes::Format() {
static const auto kResult = QString::fromLatin1("tdesktop"); static const auto kResult = QString::fromLatin1("tdesktop");
return kResult; return kResult;
@ -46,17 +63,7 @@ void CloudThemes::parseThemes(const QVector<MTPTheme> &list) {
_list.reserve(list.size()); _list.reserve(list.size());
for (const auto &theme : list) { for (const auto &theme : list) {
theme.match([&](const MTPDtheme &data) { theme.match([&](const MTPDtheme &data) {
const auto document = data.vdocument(); _list.push_back(CloudTheme::Parse(_session, data));
_list.push_back({
data.vid().v,
data.vaccess_hash().v,
qs(data.vslug()),
qs(data.vtitle()),
(document
? _session->data().processDocument(*document).get()
: nullptr),
data.is_creator()
});
}, [&](const MTPDthemeDocumentNotModified &data) { }, [&](const MTPDthemeDocumentNotModified &data) {
LOG(("API Error: Unexpected themeDocumentNotModified.")); LOG(("API Error: Unexpected themeDocumentNotModified."));
}); });

View file

@ -18,8 +18,12 @@ struct CloudTheme {
uint64 accessHash = 0; uint64 accessHash = 0;
QString slug; QString slug;
QString title; QString title;
DocumentData *document = nullptr; DocumentId documentId = 0;
bool creator = false; UserId createdBy = 0;
static CloudTheme Parse(
not_null<Main::Session*> session,
const MTPDtheme &data);
}; };
class CloudThemes final { class CloudThemes final {

View file

@ -318,7 +318,7 @@ void DocumentOpenClickHandler::Open(
LaunchWithWarning(location.name(), context); LaunchWithWarning(location.name(), context);
}; };
const auto &location = data->location(true); const auto &location = data->location(true);
if (data->isTheme() && !location.isEmpty() && location.accessEnable()) { if (data->isTheme() && data->loaded(DocumentData::FilePathResolve::Checked)) {
Core::App().showDocument(data, context); Core::App().showDocument(data, context);
location.accessDisable(); location.accessDisable();
} else if (data->canBePlayed()) { } else if (data->canBePlayed()) {

View file

@ -2192,8 +2192,10 @@ void OverlayWidget::playbackWaitingChange(bool waiting) {
void OverlayWidget::initThemePreview() { void OverlayWidget::initThemePreview() {
Assert(_doc && _doc->isTheme()); Assert(_doc && _doc->isTheme());
const auto bytes = _doc->data();
auto &location = _doc->location(); auto &location = _doc->location();
if (location.isEmpty() || !location.accessEnable()) { if (bytes.isEmpty()
&& (location.isEmpty() || !location.accessEnable())) {
return; return;
} }
_themePreviewShown = true; _themePreviewShown = true;
@ -2203,12 +2205,21 @@ void OverlayWidget::initThemePreview() {
current.backgroundImage = Window::Theme::Background()->createCurrentImage(); current.backgroundImage = Window::Theme::Background()->createCurrentImage();
current.backgroundTiled = Window::Theme::Background()->tile(); current.backgroundTiled = Window::Theme::Background()->tile();
const auto &cloudList = _doc->session().data().cloudThemes().list();
const auto i = ranges::find(
cloudList,
_doc->id,
&Data::CloudTheme::documentId);
const auto cloud = (i != end(cloudList)) ? *i : Data::CloudTheme();
const auto path = _doc->location().name(); const auto path = _doc->location().name();
const auto id = _themePreviewId = rand_value<uint64>(); const auto id = _themePreviewId = rand_value<uint64>();
const auto weak = make_weak(this); const auto weak = make_weak(this);
crl::async([=, data = std::move(current)]() mutable { crl::async([=, data = std::move(current)]() mutable {
auto preview = Window::Theme::GeneratePreview( auto preview = Window::Theme::GeneratePreview(
path, path,
bytes,
cloud,
std::move(data)); std::move(data));
crl::on_main(weak, [=, result = std::move(preview)]() mutable { crl::on_main(weak, [=, result = std::move(preview)]() mutable {
if (id != _themePreviewId) { if (id != _themePreviewId) {

View file

@ -998,9 +998,9 @@ void SetupDefaultThemes(not_null<Ui::VerticalLayout*> container) {
container.get()); container.get());
const auto chosen = [] { const auto chosen = [] {
const auto path = Window::Theme::Background()->themeAbsolutePath(); const auto &object = Window::Theme::Background()->themeObject();
for (const auto &scheme : kSchemesList) { for (const auto &scheme : kSchemesList) {
if (path == scheme.path) { if (object.pathAbsolute == scheme.path) {
return scheme.type; return scheme.type;
} }
} }
@ -1013,7 +1013,8 @@ void SetupDefaultThemes(not_null<Ui::VerticalLayout*> container) {
const auto type = scheme.type; const auto type = scheme.type;
return (type != Type::DayBlue) && (type != Type::Default); return (type != Type::DayBlue) && (type != Type::Default);
}; };
const auto currentlyIsCustom = (chosen() == Type(-1)); const auto currentlyIsCustom = (chosen() == Type(-1))
&& !Window::Theme::Background()->themeObject().cloud.id;
if (Window::Theme::IsNightMode() == isNight(scheme)) { if (Window::Theme::IsNightMode() == isNight(scheme)) {
Window::Theme::ApplyDefaultWithPath(scheme.path); Window::Theme::ApplyDefaultWithPath(scheme.path);
} else { } else {

View file

@ -67,6 +67,8 @@ constexpr auto kStickersVersionTag = quint32(-1);
constexpr auto kStickersSerializeVersion = 1; constexpr auto kStickersSerializeVersion = 1;
constexpr auto kMaxSavedStickerSetsCount = 1000; constexpr auto kMaxSavedStickerSetsCount = 1000;
const auto kThemeNewPathRelativeTag = qstr("special://new_tag");
using Database = Storage::Cache::Database; using Database = Storage::Cache::Database;
using FileKey = quint64; using FileKey = quint64;
@ -4182,50 +4184,74 @@ bool readBackground() {
} }
Window::Theme::Saved readThemeUsingKey(FileKey key) { Window::Theme::Saved readThemeUsingKey(FileKey key) {
using namespace Window::Theme;
FileReadDescriptor theme; FileReadDescriptor theme;
if (!readEncryptedFile(theme, key, FileOption::Safe, SettingsKey)) { if (!readEncryptedFile(theme, key, FileOption::Safe, SettingsKey)) {
return {}; return {};
} }
auto result = Window::Theme::Saved(); auto tag = QString();
theme.stream >> result.content; auto result = Saved();
theme.stream >> result.pathRelative >> result.pathAbsolute; auto &object = result.object;
auto &cache = result.cache;
theme.stream >> object.content;
theme.stream >> tag >> object.pathAbsolute;
const auto isCloud = (object.pathAbsolute == kThemePathAbsoluteCloud);
if (tag == kThemeNewPathRelativeTag) {
if (isCloud) {
auto creator = qint32();
theme.stream
>> object.cloud.id
>> object.cloud.accessHash
>> object.cloud.slug
>> object.cloud.title
>> object.cloud.documentId
>> creator;
object.cloud.createdBy = creator;
} else {
theme.stream >> object.pathRelative;
}
} else {
object.pathRelative = tag;
}
if (theme.stream.status() != QDataStream::Ok) { if (theme.stream.status() != QDataStream::Ok) {
return {}; return {};
} }
QFile file(result.pathRelative); auto ignoreCache = false;
if (result.pathRelative.isEmpty() || !file.exists()) { if (!isCloud) {
file.setFileName(result.pathAbsolute); QFile file(object.pathRelative);
} if (object.pathRelative.isEmpty() || !file.exists()) {
file.setFileName(object.pathAbsolute);
auto changed = false;
if (!file.fileName().isEmpty()
&& file.exists()
&& file.open(QIODevice::ReadOnly)) {
if (file.size() > kThemeFileSizeLimit) {
LOG(("Error: theme file too large: %1 "
"(should be less than 5 MB, got %2)"
).arg(file.fileName()
).arg(file.size()));
return {};
} }
auto fileContent = file.readAll(); if (!file.fileName().isEmpty()
file.close(); && file.exists()
if (result.content != fileContent) { && file.open(QIODevice::ReadOnly)) {
result.content = fileContent; if (file.size() > kThemeFileSizeLimit) {
changed = true; LOG(("Error: theme file too large: %1 "
"(should be less than 5 MB, got %2)"
).arg(file.fileName()
).arg(file.size()));
return {};
}
auto fileContent = file.readAll();
file.close();
if (object.content != fileContent) {
object.content = fileContent;
ignoreCache = true;
}
} }
} }
if (!changed) { if (!ignoreCache) {
quint32 backgroundIsTiled = 0; quint32 backgroundIsTiled = 0;
theme.stream theme.stream
>> result.cache.paletteChecksum >> cache.paletteChecksum
>> result.cache.contentChecksum >> cache.contentChecksum
>> result.cache.colors >> cache.colors
>> result.cache.background >> cache.background
>> backgroundIsTiled; >> backgroundIsTiled;
result.cache.tiled = (backgroundIsTiled == 1); cache.tiled = (backgroundIsTiled == 1);
if (theme.stream.status() != QDataStream::Ok) { if (theme.stream.status() != QDataStream::Ok) {
return {}; return {};
} }
@ -4235,20 +4261,24 @@ Window::Theme::Saved readThemeUsingKey(FileKey key) {
QString loadThemeUsingKey(FileKey key) { QString loadThemeUsingKey(FileKey key) {
auto read = readThemeUsingKey(key); auto read = readThemeUsingKey(key);
const auto result = read.pathAbsolute; const auto result = read.object.pathAbsolute;
return (!read.content.isEmpty() && Window::Theme::Load(std::move(read))) if (read.object.content.isEmpty()
? result || !Window::Theme::Load(std::move(read))) {
: QString(); return QString();
}
return result;
} }
void writeTheme(const Window::Theme::Saved &saved) { void writeTheme(const Window::Theme::Saved &saved) {
using namespace Window::Theme;
if (_themeKeyLegacy) { if (_themeKeyLegacy) {
return; return;
} }
auto &themeKey = Window::Theme::IsNightMode() auto &themeKey = IsNightMode()
? _themeKeyNight ? _themeKeyNight
: _themeKeyDay; : _themeKeyDay;
if (saved.content.isEmpty()) { if (saved.object.content.isEmpty()) {
if (themeKey) { if (themeKey) {
clearKey(themeKey); clearKey(themeKey);
themeKey = 0; themeKey = 0;
@ -4262,14 +4292,41 @@ void writeTheme(const Window::Theme::Saved &saved) {
writeSettings(); writeSettings();
} }
auto backgroundTiled = static_cast<quint32>(saved.cache.tiled ? 1 : 0); const auto &object = saved.object;
quint32 size = Serialize::bytearraySize(saved.content); const auto &cache = saved.cache;
size += Serialize::stringSize(saved.pathRelative) + Serialize::stringSize(saved.pathAbsolute); const auto tag = QString(kThemeNewPathRelativeTag);
size += sizeof(int32) * 2 + Serialize::bytearraySize(saved.cache.colors) + Serialize::bytearraySize(saved.cache.background) + sizeof(quint32); const auto isCloud = (saved.object.pathAbsolute == kThemePathAbsoluteCloud);
quint32 size = Serialize::bytearraySize(object.content);
size += Serialize::stringSize(tag) + Serialize::stringSize(object.pathAbsolute);
if (isCloud) {
size += sizeof(uint64) * 3
+ Serialize::stringSize(object.cloud.slug)
+ Serialize::stringSize(object.cloud.title)
+ sizeof(qint32);
} else {
size += Serialize::stringSize(object.pathRelative);
}
size += sizeof(int32) * 2 + Serialize::bytearraySize(cache.colors) + Serialize::bytearraySize(cache.background) + sizeof(quint32);
EncryptedDescriptor data(size); EncryptedDescriptor data(size);
data.stream << saved.content; data.stream << object.content;
data.stream << saved.pathRelative << saved.pathAbsolute; data.stream << tag << object.pathAbsolute;
data.stream << saved.cache.paletteChecksum << saved.cache.contentChecksum << saved.cache.colors << saved.cache.background << backgroundTiled; if (isCloud) {
data.stream
<< object.cloud.id
<< object.cloud.accessHash
<< object.cloud.slug
<< object.cloud.title
<< object.cloud.documentId
<< qint32(object.cloud.createdBy);
} else {
data.stream << object.pathRelative;
}
data.stream
<< cache.paletteChecksum
<< cache.contentChecksum
<< cache.colors
<< cache.background
<< quint32(cache.tiled ? 1 : 0);
FileWriteDescriptor file(themeKey, FileOption::Safe); FileWriteDescriptor file(themeKey, FileOption::Safe);
file.writeEncrypted(data, SettingsKey); file.writeEncrypted(data, SettingsKey);

View file

@ -50,8 +50,8 @@ void AbstractCheckView::setChecked(bool checked, anim::type animated) {
_checked ? 1. : 0., _checked ? 1. : 0.,
_duration); _duration);
} }
checkedChangedHook(animated);
if (changed) { if (changed) {
checkedChangedHook(animated);
_checks.fire_copy(_checked); _checks.fire_copy(_checked);
} }
} }
@ -575,7 +575,7 @@ void Checkbox::paintEvent(QPaintEvent *e) {
availableTextWidth, availableTextWidth,
width()); width());
} }
} else { } else if (_allowMultiline || _text.countHeight(width() - _st.margin.left() - _st.margin.right()) < 2 * _st.style.font->height) {
_text.drawLeft( _text.drawLeft(
p, p,
_st.margin.left(), _st.margin.left(),
@ -583,6 +583,13 @@ void Checkbox::paintEvent(QPaintEvent *e) {
width() - _st.margin.left() - _st.margin.right(), width() - _st.margin.left() - _st.margin.right(),
width(), width(),
style::al_top); style::al_top);
} else {
_text.drawLeftElided(
p,
_st.margin.left(),
textTop,
width() - _st.margin.left() - _st.margin.right(),
width());
} }
} }
} }
@ -636,7 +643,9 @@ int Checkbox::resizeGetHeight(int newWidth) {
? (newWidth - _st.margin.left() - _st.margin.right()) ? (newWidth - _st.margin.left() - _st.margin.right())
: qMax(width() - leftSkip, 1); : qMax(width() - leftSkip, 1);
const auto textBottom = _st.textPosition.y() const auto textBottom = _st.textPosition.y()
+ _text.countHeight(availableTextWidth); + ((centered && !_allowMultiline)
? _st.style.font->height
: _text.countHeight(availableTextWidth));
return std::max(result, textBottom); return std::max(result, textBottom);
} }

View file

@ -37,11 +37,8 @@ constexpr auto kNightThemeFile = str_const(":/gui/night.tdesktop-theme");
constexpr auto kMinimumTiledSize = 512; constexpr auto kMinimumTiledSize = 512;
struct Applying { struct Applying {
QString pathRelative; Saved data;
QString pathAbsolute;
QByteArray content;
QByteArray paletteForRevert; QByteArray paletteForRevert;
Cached cached;
Fn<void()> overrideKeep; Fn<void()> overrideKeep;
}; };
@ -383,15 +380,6 @@ QImage validateBackgroundImage(QImage image) {
return image; return image;
} }
void WriteAppliedTheme() {
auto saved = Saved();
saved.pathRelative = GlobalApplying.pathRelative;
saved.pathAbsolute = GlobalApplying.pathAbsolute;
saved.content = std::move(GlobalApplying.content);
saved.cache = std::move(GlobalApplying.cached);
Local::writeTheme(saved);
}
void ClearApplying() { void ClearApplying() {
GlobalApplying = Applying(); GlobalApplying = Applying();
} }
@ -556,7 +544,7 @@ void ChatBackground::set(const Data::WallPaper &paper, QImage image) {
const auto needResetAdjustable = Data::IsDefaultWallPaper(paper) const auto needResetAdjustable = Data::IsDefaultWallPaper(paper)
&& !Data::IsDefaultWallPaper(_paper) && !Data::IsDefaultWallPaper(_paper)
&& !nightMode() && !nightMode()
&& _themeAbsolutePath.isEmpty(); && _themeObject.pathAbsolute.isEmpty();
if (Data::IsThemeWallPaper(paper) && _themeImage.isNull()) { if (Data::IsThemeWallPaper(paper) && _themeImage.isNull()) {
setPaper(Data::DefaultWallPaper()); setPaper(Data::DefaultWallPaper());
} else { } else {
@ -711,10 +699,10 @@ bool ChatBackground::adjustPaletteRequired() {
} }
bool ChatBackground::isEditingTheme() const { bool ChatBackground::isEditingTheme() const {
const auto path = AreTestingTheme() const auto &object = AreTestingTheme()
? GlobalApplying.pathAbsolute ? GlobalApplying.data.object
: _themeAbsolutePath; : _themeObject;
return IsEditingTheme(path); return IsEditingTheme(object.pathAbsolute);
} }
void ChatBackground::adjustPaletteUsingBackground(const QImage &image) { void ChatBackground::adjustPaletteUsingBackground(const QImage &image) {
@ -812,12 +800,12 @@ void ChatBackground::setTileNightValue(bool tile) {
_tileNightValue = tile; _tileNightValue = tile;
} }
void ChatBackground::setThemeAbsolutePath(const QString &path) { void ChatBackground::setThemeObject(const Object &object) {
_themeAbsolutePath = path; _themeObject = object;
} }
QString ChatBackground::themeAbsolutePath() const { const Object &ChatBackground::themeObject() const {
return _themeAbsolutePath; return _themeObject;
} }
void ChatBackground::reset() { void ChatBackground::reset() {
@ -871,7 +859,7 @@ void ChatBackground::setTestingTheme(Instance &&theme) {
|| Data::IsThemeWallPaper(_paper) || Data::IsThemeWallPaper(_paper)
|| (Data::IsDefaultWallPaper(_paper) || (Data::IsDefaultWallPaper(_paper)
&& !nightMode() && !nightMode()
&& _themeAbsolutePath.isEmpty()); && _themeObject.pathAbsolute.isEmpty());
if (AreTestingTheme() && isEditingTheme()) { if (AreTestingTheme() && isEditingTheme()) {
// Grab current background image if it is not already custom // Grab current background image if it is not already custom
// Use prepared pixmap, not original image, because we're // Use prepared pixmap, not original image, because we're
@ -905,8 +893,8 @@ void ChatBackground::setTestingDefaultTheme() {
notify(BackgroundUpdate(BackgroundUpdate::Type::TestingTheme, tile()), true); notify(BackgroundUpdate(BackgroundUpdate::Type::TestingTheme, tile()), true);
} }
void ChatBackground::keepApplied(const QString &path, bool write) { void ChatBackground::keepApplied(const Object &object, bool write) {
setThemeAbsolutePath(path); setThemeObject(object);
if (Data::details::IsTestingEditorWallPaper(_paper)) { if (Data::details::IsTestingEditorWallPaper(_paper)) {
setPaper(Data::CustomWallPaper()); setPaper(Data::CustomWallPaper());
_themeImage = QImage(); _themeImage = QImage();
@ -935,15 +923,15 @@ void ChatBackground::keepApplied(const QString &path, bool write) {
bool ChatBackground::isNonDefaultThemeOrBackground() { bool ChatBackground::isNonDefaultThemeOrBackground() {
start(); start();
return nightMode() return nightMode()
? (_themeAbsolutePath != NightThemePath() ? (_themeObject.pathAbsolute != NightThemePath()
|| !Data::IsThemeWallPaper(_paper)) || !Data::IsThemeWallPaper(_paper))
: (!_themeAbsolutePath.isEmpty() : (!_themeObject.pathAbsolute.isEmpty()
|| !Data::IsDefaultWallPaper(_paper)); || !Data::IsDefaultWallPaper(_paper));
} }
bool ChatBackground::isNonDefaultBackground() { bool ChatBackground::isNonDefaultBackground() {
start(); start();
return _themeAbsolutePath.isEmpty() return _themeObject.pathAbsolute.isEmpty()
? !Data::IsDefaultWallPaper(_paper) ? !Data::IsDefaultWallPaper(_paper)
: !Data::IsThemeWallPaper(_paper); : !Data::IsThemeWallPaper(_paper);
} }
@ -987,21 +975,19 @@ void ChatBackground::toggleNightMode(std::optional<QString> themePath) {
const auto newNightMode = !_nightMode; const auto newNightMode = !_nightMode;
_nightMode = newNightMode; _nightMode = newNightMode;
auto read = settingDefault ? Saved() : Local::readThemeAfterSwitch(); auto read = settingDefault ? Saved() : Local::readThemeAfterSwitch();
auto path = read.pathAbsolute; auto path = read.object.pathAbsolute;
_nightMode = oldNightMode; _nightMode = oldNightMode;
auto oldTileValue = (_nightMode ? _tileNightValue : _tileDayValue); auto oldTileValue = (_nightMode ? _tileNightValue : _tileDayValue);
const auto alreadyOnDisk = [&] { const auto alreadyOnDisk = [&] {
if (read.content.isEmpty()) { if (read.object.content.isEmpty()) {
return false; return false;
} }
auto preview = std::make_unique<Preview>(); auto preview = std::make_unique<Preview>();
preview->pathAbsolute = std::move(read.pathAbsolute); preview->object = std::move(read.object);
preview->pathRelative = std::move(read.pathRelative);
preview->content = std::move(read.content);
preview->instance.cached = std::move(read.cache); preview->instance.cached = std::move(read.cache);
const auto loaded = loadTheme( const auto loaded = loadTheme(
preview->content, preview->object.content,
preview->instance.cached, preview->instance.cached,
ColorizerForTheme(path), ColorizerForTheme(path),
&preview->instance); &preview->instance);
@ -1026,12 +1012,13 @@ void ChatBackground::toggleNightMode(std::optional<QString> themePath) {
// Restore the value, it was set inside theme testing. // Restore the value, it was set inside theme testing.
(oldNightMode ? _tileNightValue : _tileDayValue) = oldTileValue; (oldNightMode ? _tileNightValue : _tileDayValue) = oldTileValue;
const auto saved = std::move(GlobalApplying.data);
if (!alreadyOnDisk) { if (!alreadyOnDisk) {
// First-time switch to default night mode should write it. // First-time switch to default night mode should write it.
WriteAppliedTheme(); Local::writeTheme(saved);
} }
ClearApplying(); ClearApplying();
keepApplied(path, settingDefault); keepApplied(saved.object, settingDefault);
if (tile() != _tileForRevert) { if (tile() != _tileForRevert) {
Local::writeUserSettings(); Local::writeUserSettings();
} }
@ -1049,25 +1036,25 @@ ChatBackground *Background() {
} }
bool Load(Saved &&saved) { bool Load(Saved &&saved) {
if (saved.content.size() < 4) { if (saved.object.content.size() < 4) {
LOG(("Theme Error: Could not load theme from '%1' (%2)" LOG(("Theme Error: Could not load theme from '%1' (%2)"
).arg(saved.pathRelative ).arg(saved.object.pathRelative
).arg(saved.pathAbsolute)); ).arg(saved.object.pathAbsolute));
return false; return false;
} }
GlobalBackground.createIfNull(); GlobalBackground.createIfNull();
if (loadThemeFromCache(saved.content, saved.cache)) { if (loadThemeFromCache(saved.object.content, saved.cache)) {
Background()->setThemeAbsolutePath(saved.pathAbsolute); Background()->setThemeObject(saved.object);
return true; return true;
} }
const auto colorizer = ColorizerForTheme(saved.pathAbsolute); const auto colorizer = ColorizerForTheme(saved.object.pathAbsolute);
if (!loadTheme(saved.content, saved.cache, colorizer)) { if (!loadTheme(saved.object.content, saved.cache, colorizer)) {
return false; return false;
} }
Local::writeTheme(saved); Local::writeTheme(saved);
Background()->setThemeAbsolutePath(saved.pathAbsolute); Background()->setThemeObject(saved.object);
return true; return true;
} }
@ -1077,17 +1064,15 @@ void Unload() {
} }
bool Apply(const QString &filepath) { bool Apply(const QString &filepath) {
if (auto preview = PreviewFromFile(filepath)) { if (auto preview = PreviewFromFile(filepath, {}, {})) {
return Apply(std::move(preview)); return Apply(std::move(preview));
} }
return false; return false;
} }
bool Apply(std::unique_ptr<Preview> preview) { bool Apply(std::unique_ptr<Preview> preview) {
GlobalApplying.pathRelative = std::move(preview->pathRelative); GlobalApplying.data.object = std::move(preview->object);
GlobalApplying.pathAbsolute = std::move(preview->pathAbsolute); GlobalApplying.data.cache = std::move(preview->instance.cached);
GlobalApplying.content = std::move(preview->content);
GlobalApplying.cached = std::move(preview->instance.cached);
if (GlobalApplying.paletteForRevert.isEmpty()) { if (GlobalApplying.paletteForRevert.isEmpty()) {
GlobalApplying.paletteForRevert = style::main_palette::save(); GlobalApplying.paletteForRevert = style::main_palette::save();
} }
@ -1097,14 +1082,11 @@ bool Apply(std::unique_ptr<Preview> preview) {
void ApplyDefaultWithPath(const QString &themePath) { void ApplyDefaultWithPath(const QString &themePath) {
if (!themePath.isEmpty()) { if (!themePath.isEmpty()) {
if (auto preview = PreviewFromFile(themePath)) { if (auto preview = PreviewFromFile(themePath, {}, {})) {
Apply(std::move(preview)); Apply(std::move(preview));
} }
} else { } else {
GlobalApplying.pathRelative = QString(); GlobalApplying.data = Saved();
GlobalApplying.pathAbsolute = QString();
GlobalApplying.content = QByteArray();
GlobalApplying.cached = Cached();
if (GlobalApplying.paletteForRevert.isEmpty()) { if (GlobalApplying.paletteForRevert.isEmpty()) {
GlobalApplying.paletteForRevert = style::main_palette::save(); GlobalApplying.paletteForRevert = style::main_palette::save();
} }
@ -1123,14 +1105,14 @@ bool ApplyEditedPalette(const QString &path, const QByteArray &content) {
content.constData(), content.constData(),
content.size()); content.size());
GlobalApplying.pathRelative = path.isEmpty() GlobalApplying.data.object.pathRelative = path.isEmpty()
? QString() ? QString()
: QDir().relativeFilePath(path); : QDir().relativeFilePath(path);
GlobalApplying.pathAbsolute = path.isEmpty() GlobalApplying.data.object.pathAbsolute = path.isEmpty()
? QString() ? QString()
: QFileInfo(path).absoluteFilePath(); : QFileInfo(path).absoluteFilePath();
GlobalApplying.content = content; GlobalApplying.data.object.content = content;
GlobalApplying.cached = out.cached; GlobalApplying.data.cache = out.cached;
if (GlobalApplying.paletteForRevert.isEmpty()) { if (GlobalApplying.paletteForRevert.isEmpty()) {
GlobalApplying.paletteForRevert = style::main_palette::save(); GlobalApplying.paletteForRevert = style::main_palette::save();
} }
@ -1150,10 +1132,10 @@ void KeepApplied() {
onstack(); onstack();
return; return;
} }
const auto path = GlobalApplying.pathAbsolute; const auto saved = std::move(GlobalApplying.data);
WriteAppliedTheme(); Local::writeTheme(saved);
ClearApplying(); ClearApplying();
Background()->keepApplied(path, true); Background()->keepApplied(saved.object, true);
} }
void Revert() { void Revert() {

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once #pragma once
#include "data/data_wall_paper.h" #include "data/data_wall_paper.h"
#include "data/data_cloud_themes.h"
namespace Main { namespace Main {
class Session; class Session;
@ -16,8 +17,15 @@ class Session;
namespace Window { namespace Window {
namespace Theme { namespace Theme {
constexpr auto kThemeSchemeSizeLimit = 1024 * 1024; inline constexpr auto kThemeSchemeSizeLimit = 1024 * 1024;
inline const auto kThemePathAbsoluteCloud = qstr("special://cloud");
struct Object {
QString pathRelative;
QString pathAbsolute;
QByteArray content;
Data::CloudTheme cloud;
};
struct Cached { struct Cached {
QByteArray colors; QByteArray colors;
QByteArray background; QByteArray background;
@ -26,9 +34,7 @@ struct Cached {
int32 contentChecksum = 0; int32 contentChecksum = 0;
}; };
struct Saved { struct Saved {
QString pathRelative; Object object;
QString pathAbsolute;
QByteArray content;
Cached cache; Cached cache;
}; };
bool Load(Saved &&saved); bool Load(Saved &&saved);
@ -42,10 +48,8 @@ struct Instance {
}; };
struct Preview { struct Preview {
QString pathRelative; Object object;
QString pathAbsolute;
Instance instance; Instance instance;
QByteArray content;
QImage preview; QImage preview;
}; };
@ -107,8 +111,8 @@ public:
void setTile(bool tile); void setTile(bool tile);
void setTileDayValue(bool tile); void setTileDayValue(bool tile);
void setTileNightValue(bool tile); void setTileNightValue(bool tile);
void setThemeAbsolutePath(const QString &path); void setThemeObject(const Object &object);
[[nodiscard]] QString themeAbsolutePath() const; [[nodiscard]] const Object &themeObject() const;
[[nodiscard]] bool isEditingTheme() const; [[nodiscard]] bool isEditingTheme() const;
void reset(); void reset();
@ -159,7 +163,7 @@ private:
void setNightModeValue(bool nightMode); void setNightModeValue(bool nightMode);
[[nodiscard]] bool nightMode() const; [[nodiscard]] bool nightMode() const;
void toggleNightMode(std::optional<QString> themePath); void toggleNightMode(std::optional<QString> themePath);
void keepApplied(const QString &path, bool write); void keepApplied(const Object &object, bool write);
[[nodiscard]] bool isNonDefaultThemeOrBackground(); [[nodiscard]] bool isNonDefaultThemeOrBackground();
[[nodiscard]] bool isNonDefaultBackground(); [[nodiscard]] bool isNonDefaultBackground();
void checkUploadWallPaper(); void checkUploadWallPaper();
@ -183,7 +187,7 @@ private:
bool _isMonoColorImage = false; bool _isMonoColorImage = false;
QString _themeAbsolutePath; Object _themeObject;
QImage _themeImage; QImage _themeImage;
bool _themeTile = false; bool _themeTile = false;

View file

@ -908,24 +908,42 @@ void Generator::restoreTextPalette() {
} // namespace } // namespace
std::unique_ptr<Preview> PreviewFromFile(const QString &filepath) { std::unique_ptr<Preview> PreviewFromFile(
const QString &filepath,
const QByteArray &bytes,
const Data::CloudTheme &cloud) {
auto result = std::make_unique<Preview>(); auto result = std::make_unique<Preview>();
result->pathRelative = filepath.isEmpty() auto &object = result->object;
? QString() object.cloud = cloud;
: QDir().relativeFilePath(filepath); if (cloud.documentId || filepath.isEmpty()) {
result->pathAbsolute = filepath.isEmpty() object.pathRelative = QString();
? QString() object.pathAbsolute = QString(kThemePathAbsoluteCloud);
: QFileInfo(filepath).absoluteFilePath(); } else {
if (!LoadFromFile(filepath, &result->instance, &result->content)) { object.pathRelative = filepath.isEmpty()
return nullptr; ? QString()
: QDir().relativeFilePath(filepath);
object.pathAbsolute = filepath.isEmpty()
? QString()
: QFileInfo(filepath).absoluteFilePath();
}
if (bytes.isEmpty()) {
if (!LoadFromFile(filepath, &result->instance, &object.content)) {
return nullptr;
}
} else {
if (!LoadFromContent(bytes, &result->instance)) {
return nullptr;
}
} }
return result; return result;
} }
std::unique_ptr<Preview> GeneratePreview( std::unique_ptr<Preview> GeneratePreview(
const QString &filepath, const QString &filepath,
const QByteArray &bytes,
const Data::CloudTheme &cloud,
CurrentData &&data) { CurrentData &&data) {
auto result = PreviewFromFile(filepath); auto result = PreviewFromFile(filepath, bytes, cloud);
if (!result) { if (!result) {
return nullptr; return nullptr;
} }

View file

@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
namespace Data {
struct CloudTheme;
} // namespace Data
namespace Window { namespace Window {
namespace Theme { namespace Theme {
@ -18,9 +22,14 @@ struct CurrentData {
bool backgroundTiled = false; bool backgroundTiled = false;
}; };
std::unique_ptr<Preview> PreviewFromFile(const QString &filepath); std::unique_ptr<Preview> PreviewFromFile(
const QString &filepath,
const QByteArray &bytes,
const Data::CloudTheme &cloud);
std::unique_ptr<Preview> GeneratePreview( std::unique_ptr<Preview> GeneratePreview(
const QString &filepath, const QString &filepath,
const QByteArray &bytes,
const Data::CloudTheme &cloud,
CurrentData &&data); CurrentData &&data);
int DefaultPreviewTitleHeight(); int DefaultPreviewTitleHeight();

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_cloud_themes.h" #include "data/data_cloud_themes.h"
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_session.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "styles/style_settings.h" #include "styles/style_settings.h"
@ -222,6 +223,11 @@ void CloudListBox(
box->setTitle(tr::lng_settings_bg_cloud_themes()); box->setTitle(tr::lng_settings_bg_cloud_themes());
box->setWidth(st::boxWideWidth); box->setWidth(st::boxWideWidth);
const auto currentId = Background()->themeObject().cloud.documentId;
ranges::stable_sort(list, std::less<>(), [&](const Data::CloudTheme &t) {
return !t.documentId ? 2 : (t.documentId == currentId) ? 0 : 1;
});
const auto content = box->addRow( const auto content = box->addRow(
object_ptr<Ui::RpWidget>(box), object_ptr<Ui::RpWidget>(box),
style::margins( style::margins(
@ -229,7 +235,27 @@ void CloudListBox(
0, 0,
st::settingsSubsectionTitlePadding.right(), st::settingsSubsectionTitlePadding.right(),
0)); 0));
const auto group = std::make_shared<Ui::RadiobuttonGroup>(-1); const auto group = std::make_shared<Ui::RadiobuttonGroup>();
const auto resolveCurrent = [=] {
const auto currentId = Background()->themeObject().cloud.id;
const auto i = currentId
? ranges::find(list, currentId, &Data::CloudTheme::id)
: end(list);
group->setValue(i - begin(list));
};
resolveCurrent();
auto checker = Background()->add_subscription([=](const BackgroundUpdate &update) {
if (update.type == BackgroundUpdate::Type::ApplyingTheme
|| update.type == BackgroundUpdate::Type::New) {
resolveCurrent();
}
});
group->setChangedCallback([=](int selected) {
resolveCurrent();
});
Ui::AttachAsChild(box, std::move(checker));
const auto waiting = std::make_shared<std::vector<WaitingPair>>(); const auto waiting = std::make_shared<std::vector<WaitingPair>>();
const auto fallback = std::make_shared<QImage>(); const auto fallback = std::make_shared<QImage>();
const auto buttonsMap = std::make_shared<base::flat_map< const auto buttonsMap = std::make_shared<base::flat_map<
@ -248,13 +274,13 @@ void CloudListBox(
list list
) | ranges::view::transform([&](const Data::CloudTheme &theme) ) | ranges::view::transform([&](const Data::CloudTheme &theme)
-> not_null<Ui::RpWidget*> { -> not_null<Ui::RpWidget*> {
const auto document = theme.document; if (!theme.documentId) {
if (!document) { index++;
return Ui::CreateChild<Ui::RpWidget>(content); return Ui::CreateChild<Ui::RpWidget>(content);
} }
if (document) { const auto document = window->session().data().document(
document->save(Data::FileOrigin(), QString()); // #TODO themes theme.documentId);
} document->save(Data::FileOrigin(), QString()); // #TODO themes
auto colors = ColorsFromTheme( auto colors = ColorsFromTheme(
document->filepath(), document->filepath(),
document->data()); document->data());