Show custom chat wallpapers in chats.

This commit is contained in:
John Preston 2023-04-17 13:43:08 +04:00
parent 5cbf9a2dc4
commit bf27185feb
20 changed files with 369 additions and 121 deletions

View file

@ -764,7 +764,10 @@ bool ResolveTestChatTheme(
} }
const auto recache = [&](Data::CloudThemeType type) { const auto recache = [&](Data::CloudThemeType type) {
[[maybe_unused]] auto value = theme->settings.contains(type) [[maybe_unused]] auto value = theme->settings.contains(type)
? controller->cachedChatThemeValue(*theme, type) ? controller->cachedChatThemeValue(
*theme,
Data::WallPaper(0),
type)
: nullptr; : nullptr;
}; };
recache(Data::CloudThemeType::Dark); recache(Data::CloudThemeType::Dark);

View file

@ -64,45 +64,46 @@ struct PeerUpdate {
Migration = (1ULL << 5), Migration = (1ULL << 5),
UnavailableReason = (1ULL << 6), UnavailableReason = (1ULL << 6),
ChatThemeEmoji = (1ULL << 7), ChatThemeEmoji = (1ULL << 7),
IsBlocked = (1ULL << 8), ChatWallPaper = (1ULL << 8),
MessagesTTL = (1ULL << 9), IsBlocked = (1ULL << 9),
FullInfo = (1ULL << 10), MessagesTTL = (1ULL << 10),
Usernames = (1ULL << 11), FullInfo = (1ULL << 11),
TranslationDisabled = (1ULL << 12), Usernames = (1ULL << 12),
TranslationDisabled = (1ULL << 13),
// For users // For users
CanShareContact = (1ULL << 13), CanShareContact = (1ULL << 14),
IsContact = (1ULL << 14), IsContact = (1ULL << 15),
PhoneNumber = (1ULL << 15), PhoneNumber = (1ULL << 16),
OnlineStatus = (1ULL << 16), OnlineStatus = (1ULL << 17),
BotCommands = (1ULL << 17), BotCommands = (1ULL << 18),
BotCanBeInvited = (1ULL << 18), BotCanBeInvited = (1ULL << 19),
BotStartToken = (1ULL << 19), BotStartToken = (1ULL << 20),
CommonChats = (1ULL << 20), CommonChats = (1ULL << 21),
HasCalls = (1ULL << 21), HasCalls = (1ULL << 22),
SupportInfo = (1ULL << 22), SupportInfo = (1ULL << 23),
IsBot = (1ULL << 23), IsBot = (1ULL << 24),
EmojiStatus = (1ULL << 24), EmojiStatus = (1ULL << 25),
// For chats and channels // For chats and channels
InviteLinks = (1ULL << 25), InviteLinks = (1ULL << 26),
Members = (1ULL << 26), Members = (1ULL << 27),
Admins = (1ULL << 27), Admins = (1ULL << 28),
BannedUsers = (1ULL << 28), BannedUsers = (1ULL << 29),
Rights = (1ULL << 29), Rights = (1ULL << 30),
PendingRequests = (1ULL << 30), PendingRequests = (1ULL << 31),
Reactions = (1ULL << 31), Reactions = (1ULL << 32),
// For channels // For channels
ChannelAmIn = (1ULL << 32), ChannelAmIn = (1ULL << 33),
StickersSet = (1ULL << 33), StickersSet = (1ULL << 34),
ChannelLinkedChat = (1ULL << 34), ChannelLinkedChat = (1ULL << 35),
ChannelLocation = (1ULL << 35), ChannelLocation = (1ULL << 36),
Slowmode = (1ULL << 36), Slowmode = (1ULL << 37),
GroupCall = (1ULL << 37), GroupCall = (1ULL << 38),
// For iteration // For iteration
LastUsedBit = (1ULL << 37), LastUsedBit = (1ULL << 38),
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; } friend inline constexpr auto is_flag_type(Flag) { return true; }

View file

@ -794,7 +794,7 @@ QString DocumentData::loadingFilePath() const {
bool DocumentData::displayLoading() const { bool DocumentData::displayLoading() const {
return loading() return loading()
? (!_loader->loadingLocal() || !_loader->autoLoading()) ? !_loader->loadingLocal()
: (uploading() && !waitingForAlbum()); : (uploading() && !waitingForAlbum());
} }

View file

@ -1093,6 +1093,22 @@ const QString &PeerData::themeEmoji() const {
return _themeEmoticon; return _themeEmoticon;
} }
void PeerData::setWallPaper(std::optional<Data::WallPaper> paper) {
if (!paper && !_wallPaper) {
return;
} else if (paper && _wallPaper && _wallPaper->equals(*paper)) {
return;
}
_wallPaper = paper
? std::make_unique<Data::WallPaper>(std::move(*paper))
: nullptr;
session().changes().peerUpdated(this, UpdateFlag::ChatWallPaper);
}
const Data::WallPaper *PeerData::wallPaper() const {
return _wallPaper.get();
}
void PeerData::setIsBlocked(bool is) { void PeerData::setIsBlocked(bool is) {
const auto status = is const auto status = is
? BlockStatus::Blocked ? BlockStatus::Blocked

View file

@ -37,6 +37,7 @@ class ForumTopic;
class Session; class Session;
class GroupCall; class GroupCall;
struct ReactionId; struct ReactionId;
class WallPaper;
[[nodiscard]] int PeerColorIndex(PeerId peerId); [[nodiscard]] int PeerColorIndex(PeerId peerId);
@ -403,6 +404,9 @@ public:
void setThemeEmoji(const QString &emoticon); void setThemeEmoji(const QString &emoticon);
[[nodiscard]] const QString &themeEmoji() const; [[nodiscard]] const QString &themeEmoji() const;
void setWallPaper(std::optional<Data::WallPaper> paper);
[[nodiscard]] const Data::WallPaper *wallPaper() const;
const PeerId id; const PeerId id;
MTPinputPeer input = MTP_inputPeerEmpty(); MTPinputPeer input = MTP_inputPeerEmpty();
@ -457,6 +461,7 @@ private:
QString _about; QString _about;
QString _themeEmoticon; QString _themeEmoticon;
std::unique_ptr<Data::WallPaper> _wallPaper;
}; };

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo.h" #include "data/data_photo.h"
#include "data/data_emoji_statuses.h" #include "data/data_emoji_statuses.h"
#include "data/data_user_names.h" #include "data/data_user_names.h"
#include "data/data_wall_paper.h"
#include "data/notify/data_notify_settings.h" #include "data/notify/data_notify_settings.h"
#include "api/api_peer_photo.h" #include "api/api_peer_photo.h"
#include "apiwrap.h" #include "apiwrap.h"
@ -462,6 +463,13 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
} }
} }
if (const auto paper = update.vwallpaper()) {
user->setWallPaper(
Data::WallPaper::Create(&user->session(), *paper));
} else {
user->setWallPaper({});
}
user->fullUpdated(); user->fullUpdated();
} }

View file

@ -198,6 +198,16 @@ WallPaperId WallPaper::id() const {
return _id; return _id;
} }
bool WallPaper::equals(const WallPaper &paper) const {
return (_flags == paper._flags)
&& (_slug == paper._slug)
&& (_backgroundColors == paper._backgroundColors)
&& (_rotation == paper._rotation)
&& (_intensity == paper._intensity)
&& (_blurred == paper._blurred)
&& (_document == paper._document);
}
const std::vector<QColor> WallPaper::backgroundColors() const { const std::vector<QColor> WallPaper::backgroundColors() const {
return _backgroundColors; return _backgroundColors;
} }
@ -251,34 +261,54 @@ bool WallPaper::hasShareUrl() const {
return !_slug.isEmpty(); return !_slug.isEmpty();
} }
QString WallPaper::shareUrl(not_null<Main::Session*> session) const { QStringList WallPaper::collectShareParams() const {
if (!hasShareUrl()) { auto result = QStringList();
return QString();
}
const auto base = session->createInternalLinkFull("bg/" + _slug);
auto params = QStringList();
if (isPattern()) { if (isPattern()) {
if (!backgroundColors().empty()) { if (!backgroundColors().empty()) {
params.push_back( result.push_back(
"bg_color=" + StringFromColors(backgroundColors())); "bg_color=" + StringFromColors(backgroundColors()));
} }
if (_intensity) { if (_intensity) {
params.push_back("intensity=" + QString::number(_intensity)); result.push_back("intensity=" + QString::number(_intensity));
} }
} }
if (_rotation && backgroundColors().size() == 2) { if (_rotation && backgroundColors().size() == 2) {
params.push_back("rotation=" + QString::number(_rotation)); result.push_back("rotation=" + QString::number(_rotation));
} }
auto mode = QStringList(); auto mode = QStringList();
if (_blurred) { if (_blurred) {
mode.push_back("blur"); mode.push_back("blur");
} }
if (!mode.isEmpty()) { if (!mode.isEmpty()) {
params.push_back("mode=" + mode.join('+')); result.push_back("mode=" + mode.join('+'));
} }
return params.isEmpty() return result;
? base }
: base + '?' + params.join('&');
bool WallPaper::isNull() const {
return !_id && _slug.isEmpty() && _backgroundColors.empty();
}
QString WallPaper::key() const {
if (isNull()) {
return QString();
}
const auto base = _slug.isEmpty()
? (_id
? QString::number(_id)
: StringFromColors(backgroundColors()))
: ("bg/" + _slug);
const auto params = collectShareParams();
return params.isEmpty() ? base : (base + '?' + params.join('&'));
}
QString WallPaper::shareUrl(not_null<Main::Session*> session) const {
if (!hasShareUrl()) {
return QString();
}
const auto base = session->createInternalLinkFull("bg/" + _slug);
const auto params = collectShareParams();
return params.isEmpty() ? base : (base + '?' + params.join('&'));
} }
void WallPaper::loadDocumentThumbnail() const { void WallPaper::loadDocumentThumbnail() const {

View file

@ -42,7 +42,11 @@ public:
void setLocalImageAsThumbnail(std::shared_ptr<Image> image); void setLocalImageAsThumbnail(std::shared_ptr<Image> image);
[[nodiscard]] bool equals(const WallPaper &paper) const;
[[nodiscard]] WallPaperId id() const; [[nodiscard]] WallPaperId id() const;
[[nodiscard]] bool isNull() const;
[[nodiscard]] QString key() const;
[[nodiscard]] const std::vector<QColor> backgroundColors() const; [[nodiscard]] const std::vector<QColor> backgroundColors() const;
[[nodiscard]] DocumentData *document() const; [[nodiscard]] DocumentData *document() const;
[[nodiscard]] Image *localThumbnail() const; [[nodiscard]] Image *localThumbnail() const;
@ -103,6 +107,8 @@ public:
private: private:
static constexpr auto kDefaultIntensity = 50; static constexpr auto kDefaultIntensity = 50;
[[nodiscard]] QStringList collectShareParams() const;
WallPaperId _id = WallPaperId(); WallPaperId _id = WallPaperId();
uint64 _accessHash = 0; uint64 _accessHash = 0;
UserId _ownerId = 0; UserId _ownerId = 0;

View file

@ -1264,6 +1264,9 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
if (!item->unread(this)) { if (!item->unread(this)) {
outboxRead(item); outboxRead(item);
} }
if (item->changesWallPaper()) {
peer->updateFullForced();
}
} else { } else {
if (item->unread(this)) { if (item->unread(this)) {
if (unreadCountKnown()) { if (unreadCountKnown()) {

View file

@ -2213,6 +2213,13 @@ bool HistoryItem::hasDirectLink() const {
return isRegular() && _history->peer->isChannel(); return isRegular() && _history->peer->isChannel();
} }
bool HistoryItem::changesWallPaper() const {
if (const auto media = _media.get()) {
return media->paper() != nullptr;
}
return Has<HistoryServiceSameBackground>();
}
FullMsgId HistoryItem::fullId() const { FullMsgId HistoryItem::fullId() const {
return FullMsgId(_history->peer->id, id); return FullMsgId(_history->peer->id, id);
} }

View file

@ -433,6 +433,7 @@ public:
[[nodiscard]] crl::time lastReactionsRefreshTime() const; [[nodiscard]] crl::time lastReactionsRefreshTime() const;
[[nodiscard]] bool hasDirectLink() const; [[nodiscard]] bool hasDirectLink() const;
[[nodiscard]] bool changesWallPaper() const;
[[nodiscard]] FullMsgId fullId() const; [[nodiscard]] FullMsgId fullId() const;
[[nodiscard]] GlobalMsgId globalId() const; [[nodiscard]] GlobalMsgId globalId() const;

View file

@ -27,6 +27,12 @@ ServiceBox::ServiceBox(
, _content(std::move(content)) , _content(std::move(content))
, _button([&] { , _button([&] {
auto result = Button(); auto result = Button();
result.link = _content->createViewLink();
const auto text = _content->button();
if (text.isEmpty()) {
return result;
}
result.repaint = [=] { repaint(); }; result.repaint = [=] { repaint(); };
result.text.setText(st::semiboldTextStyle, _content->button()); result.text.setText(st::semiboldTextStyle, _content->button());
@ -39,8 +45,6 @@ ServiceBox::ServiceBox(
+ padding.right(), + padding.right(),
height); height);
result.link = _content->createViewLink();
return result; return result;
}()) }())
, _maxWidth(st::msgServiceGiftBoxSize.width() , _maxWidth(st::msgServiceGiftBoxSize.width()
@ -67,8 +71,10 @@ ServiceBox::ServiceBox(
: (_title.countHeight(_maxWidth) : (_title.countHeight(_maxWidth)
+ st::msgServiceGiftBoxTitlePadding.bottom())) + st::msgServiceGiftBoxTitlePadding.bottom()))
+ _subtitle.countHeight(_maxWidth) + _subtitle.countHeight(_maxWidth)
+ st::msgServiceGiftBoxButtonMargins.top() + (_button.empty()
+ _button.size.height() ? 0
: (st::msgServiceGiftBoxButtonMargins.top()
+ _button.size.height()))
+ st::msgServiceGiftBoxButtonMargins.bottom())) + st::msgServiceGiftBoxButtonMargins.bottom()))
, _innerSize(_size - QSize(0, st::msgServiceGiftBoxTopSkip)) { , _innerSize(_size - QSize(0, st::msgServiceGiftBoxTopSkip)) {
} }
@ -106,7 +112,7 @@ void ServiceBox::draw(Painter &p, const PaintContext &context) const {
top += _subtitle.countHeight(_maxWidth) + padding.bottom(); top += _subtitle.countHeight(_maxWidth) + padding.bottom();
} }
{ if (!_button.empty()) {
const auto position = buttonRect().topLeft(); const auto position = buttonRect().topLeft();
p.translate(position); p.translate(position);
@ -142,7 +148,11 @@ void ServiceBox::draw(Painter &p, const PaintContext &context) const {
TextState ServiceBox::textState(QPoint point, StateRequest request) const { TextState ServiceBox::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent); auto result = TextState(_parent);
{ if (_button.empty()) {
if (QRect(QPoint(), _innerSize).contains(point)) {
result.link = _button.link;
}
} else {
const auto rect = buttonRect(); const auto rect = buttonRect();
if (rect.contains(point)) { if (rect.contains(point)) {
result.link = _button.link; result.link = _button.link;
@ -214,7 +224,9 @@ QRect ServiceBox::contentRect() const {
} }
void ServiceBox::Button::toggleRipple(bool pressed) { void ServiceBox::Button::toggleRipple(bool pressed) {
if (pressed) { if (empty()) {
return;
} else if (pressed) {
const auto linkWidth = size.width(); const auto linkWidth = size.width();
const auto linkHeight = size.height(); const auto linkHeight = size.height();
if (!ripple) { if (!ripple) {
@ -234,6 +246,10 @@ void ServiceBox::Button::toggleRipple(bool pressed) {
} }
} }
bool ServiceBox::Button::empty() const {
return text.isEmpty();
}
void ServiceBox::Button::drawBg(QPainter &p) const { void ServiceBox::Button::drawBg(QPainter &p) const {
const auto radius = size.height() / 2.; const auto radius = size.height() / 2.;
p.drawRoundedRect(0, 0, size.width(), size.height(), radius, radius); p.drawRoundedRect(0, 0, size.width(), size.height(), radius, radius);

View file

@ -88,6 +88,7 @@ private:
struct Button { struct Button {
void drawBg(QPainter &p) const; void drawBg(QPainter &p) const;
void toggleRipple(bool pressed); void toggleRipple(bool pressed);
[[nodiscard]] bool empty() const;
Fn<void()> repaint; Fn<void()> repaint;

View file

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/data_wall_paper.h" #include "data/data_wall_paper.h"
#include "base/qthelp_url.h" #include "base/qthelp_url.h"
#include "core/click_handler_types.h"
#include "core/local_url_handlers.h" #include "core/local_url_handlers.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
@ -27,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/cached_round_corners.h" #include "ui/cached_round_corners.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
namespace HistoryView { namespace HistoryView {
@ -433,15 +435,21 @@ QString ThemeDocumentBox::subtitle() {
} }
QString ThemeDocumentBox::button() { QString ThemeDocumentBox::button() {
return tr::lng_sticker_premium_view(tr::now); return _parent->data()->out()
? QString()
: tr::lng_action_set_wallpaper_button(tr::now);
} }
ClickHandlerPtr ThemeDocumentBox::createViewLink() { ClickHandlerPtr ThemeDocumentBox::createViewLink() {
const auto out = _parent->data()->out();
const auto to = _parent->history()->peer; const auto to = _parent->history()->peer;
return std::make_shared<LambdaClickHandler>([=](ClickContext context) { return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
//const auto my = context.other.value<ClickHandlerContext>(); const auto my = context.other.value<ClickHandlerContext>();
//if (const auto controller = my.sessionWindow.get()) { if (const auto controller = my.sessionWindow.get()) {
//} if (out) {
controller->toggleChooseChatTheme(to);
}
}
}); });
} }

View file

@ -163,7 +163,8 @@ constexpr auto kMinAcceptableContrast = 1.14;// 4.5;
} // namespace } // namespace
bool operator==(const ChatThemeBackground &a, const ChatThemeBackground &b) { bool operator==(const ChatThemeBackground &a, const ChatThemeBackground &b) {
return (a.prepared.cacheKey() == b.prepared.cacheKey()) return (a.key == b.key)
&& (a.prepared.cacheKey() == b.prepared.cacheKey())
&& (a.gradientForFill.cacheKey() == b.gradientForFill.cacheKey()) && (a.gradientForFill.cacheKey() == b.gradientForFill.cacheKey())
&& (a.tile == b.tile) && (a.tile == b.tile)
&& (a.patternOpacity == b.patternOpacity); && (a.patternOpacity == b.patternOpacity);
@ -222,9 +223,14 @@ void ChatTheme::adjustPalette(const ChatThemeDescriptor &descriptor) {
if (overrideOutBg) { if (overrideOutBg) {
set(p.msgOutBg(), descriptor.bubblesData.colors.front()); set(p.msgOutBg(), descriptor.bubblesData.colors.front());
} }
const auto &background = descriptor.backgroundData.colors; const auto &data = descriptor.backgroundData;
if (!background.empty()) { const auto &background = data.colors;
const auto average = CountAverageColor(background); const auto useImage = !data.isPattern
&& (!data.path.isEmpty() || !data.bytes.isEmpty());
if (useImage || !background.empty()) {
const auto average = useImage
? Ui::CountAverageColor(_mutableBackground.prepared)
: CountAverageColor(background);
adjust(p.msgServiceBg(), average); adjust(p.msgServiceBg(), average);
adjust(p.msgServiceBgSelected(), average); adjust(p.msgServiceBgSelected(), average);
adjust(p.historyScrollBg(), average); adjust(p.historyScrollBg(), average);
@ -407,6 +413,7 @@ void ChatTheme::setBackground(ChatThemeBackground &&background) {
} }
void ChatTheme::updateBackgroundImageFrom(ChatThemeBackground &&background) { void ChatTheme::updateBackgroundImageFrom(ChatThemeBackground &&background) {
_mutableBackground.key = background.key;
_mutableBackground.prepared = std::move(background.prepared); _mutableBackground.prepared = std::move(background.prepared);
_mutableBackground.preparedForTiled = std::move( _mutableBackground.preparedForTiled = std::move(
background.preparedForTiled); background.preparedForTiled);
@ -1028,6 +1035,16 @@ ChatThemeBackground PrepareBackgroundImage(
} else if (data.colors.empty()) { } else if (data.colors.empty()) {
prepared.setDevicePixelRatio(style::DevicePixelRatio()); prepared.setDevicePixelRatio(style::DevicePixelRatio());
} }
if (!prepared.isNull()
&& !data.isPattern
&& data.forDarkMode
&& data.darkModeDimming > 0) {
const auto ratio = int(prepared.devicePixelRatio());
auto p = QPainter(&prepared);
p.fillRect(
QRect(0, 0, prepared.width() / ratio, prepared.height() / ratio),
QColor(0, 0, 0, 255 * data.darkModeDimming / 100));
}
const auto imageMonoColor = (data.colors.size() < 2) const auto imageMonoColor = (data.colors.size() < 2)
? CalculateImageMonoColor(prepared) ? CalculateImageMonoColor(prepared)
: std::nullopt; : std::nullopt;
@ -1038,6 +1055,7 @@ ChatThemeBackground PrepareBackgroundImage(
? Ui::GenerateDitheredGradient(data.colors, data.gradientRotation) ? Ui::GenerateDitheredGradient(data.colors, data.gradientRotation)
: QImage(); : QImage();
return ChatThemeBackground{ return ChatThemeBackground{
.key = data.key,
.prepared = prepared, .prepared = prepared,
.preparedForTiled = PrepareImageForTiled(prepared), .preparedForTiled = PrepareImageForTiled(prepared),
.gradientForFill = std::move(gradientForFill), .gradientForFill = std::move(gradientForFill),

View file

@ -23,6 +23,7 @@ struct ChatPaintContext;
struct BubblePattern; struct BubblePattern;
struct ChatThemeBackground { struct ChatThemeBackground {
QString key;
QImage prepared; QImage prepared;
QImage preparedForTiled; QImage preparedForTiled;
QImage gradientForFill; QImage gradientForFill;
@ -42,13 +43,16 @@ bool operator==(const ChatThemeBackground &a, const ChatThemeBackground &b);
bool operator!=(const ChatThemeBackground &a, const ChatThemeBackground &b); bool operator!=(const ChatThemeBackground &a, const ChatThemeBackground &b);
struct ChatThemeBackgroundData { struct ChatThemeBackgroundData {
QString key;
QString path; QString path;
QByteArray bytes; QByteArray bytes;
bool gzipSvg = false; bool gzipSvg = false;
std::vector<QColor> colors; std::vector<QColor> colors;
bool isPattern = false; bool isPattern = false;
float64 patternOpacity = 0.; float64 patternOpacity = 0.;
int darkModeDimming = 0;
bool isBlurred = false; bool isBlurred = false;
bool forDarkMode = false;
bool generateGradient = false; bool generateGradient = false;
int gradientRotation = 0; int gradientRotation = 0;
}; };
@ -113,26 +117,10 @@ struct ChatThemeKey {
explicit operator bool() const { explicit operator bool() const {
return (id != 0); return (id != 0);
} }
};
inline bool operator<(ChatThemeKey a, ChatThemeKey b) { friend inline auto operator<=>(ChatThemeKey, ChatThemeKey) = default;
return (a.id < b.id) || ((a.id == b.id) && (a.dark < b.dark)); friend inline bool operator==(ChatThemeKey, ChatThemeKey) = default;
} };
inline bool operator>(ChatThemeKey a, ChatThemeKey b) {
return (b < a);
}
inline bool operator<=(ChatThemeKey a, ChatThemeKey b) {
return !(b < a);
}
inline bool operator>=(ChatThemeKey a, ChatThemeKey b) {
return !(a < b);
}
inline bool operator==(ChatThemeKey a, ChatThemeKey b) {
return (a.id == b.id) && (a.dark == b.dark);
}
inline bool operator!=(ChatThemeKey a, ChatThemeKey b) {
return !(a == b);
}
struct ChatThemeDescriptor { struct ChatThemeDescriptor {
ChatThemeKey key; ChatThemeKey key;

View file

@ -383,7 +383,10 @@ void ChooseThemeController::initList() {
_chosen = chosen; _chosen = chosen;
entry->chosen = true; entry->chosen = true;
if (entry->theme || !entry->key) { if (entry->theme || !entry->key) {
_controller->overridePeerTheme(_peer, entry->theme); _controller->overridePeerTheme(
_peer,
entry->theme,
entry->emoji);
} }
_inner->update(); _inner->update();
} }
@ -534,6 +537,7 @@ void ChooseThemeController::fill(
}); });
_controller->cachedChatThemeValue( _controller->cachedChatThemeValue(
theme, theme,
Data::WallPaper(0),
type type
) | rpl::filter([=](const std::shared_ptr<ChatTheme> &data) { ) | rpl::filter([=](const std::shared_ptr<ChatTheme> &data) {
return data && (data->key() == key); return data && (data->key() == key);
@ -549,7 +553,10 @@ void ChooseThemeController::fill(
i->theme = std::move(data); i->theme = std::move(data);
i->preview = GeneratePreview(theme); i->preview = GeneratePreview(theme);
if (_chosen == i->emoji->text()) { if (_chosen == i->emoji->text()) {
_controller->overridePeerTheme(_peer, i->theme); _controller->overridePeerTheme(
_peer,
i->theme,
i->emoji);
} }
_inner->update(); _inner->update();

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_cloud_themes.h" #include "data/data_cloud_themes.h"
@ -45,6 +46,44 @@ namespace {
}); });
} }
struct ResolvedPaper {
Data::WallPaper paper;
std::shared_ptr<Data::DocumentMedia> media;
};
[[nodiscard]] rpl::producer<std::optional<ResolvedPaper>> PeerWallPaperValue(
not_null<PeerData*> peer) {
return peer->session().changes().peerFlagsValue(
peer,
Data::PeerUpdate::Flag::ChatWallPaper
) | rpl::map([=]() -> rpl::producer<std::optional<ResolvedPaper>> {
const auto paper = peer->wallPaper();
const auto single = [](std::optional<ResolvedPaper> value) {
return rpl::single(std::move(value));
};
if (!paper) {
return single({});
}
const auto document = paper->document();
auto value = ResolvedPaper{
*paper,
document ? document->createMediaView() : nullptr,
};
if (!value.media || value.media->loaded(true)) {
return single(std::move(value));
}
paper->loadDocument();
return single(
value
) | rpl::then(document->session().downloaderTaskFinished(
) | rpl::filter([=] {
return value.media->loaded(true);
}) | rpl::take(1) | rpl::map_to(
std::optional<ResolvedPaper>(value)
));
}) | rpl::flatten_latest();
}
[[nodiscard]] auto MaybeChatThemeDataValueFromPeer( [[nodiscard]] auto MaybeChatThemeDataValueFromPeer(
not_null<PeerData*> peer) not_null<PeerData*> peer)
-> rpl::producer<std::optional<Data::CloudTheme>> { -> rpl::producer<std::optional<Data::CloudTheme>> {
@ -58,6 +97,7 @@ namespace {
struct ResolvedTheme { struct ResolvedTheme {
std::optional<Data::CloudTheme> theme; std::optional<Data::CloudTheme> theme;
std::optional<ResolvedPaper> paper;
bool dark = false; bool dark = false;
}; };
@ -66,9 +106,13 @@ struct ResolvedTheme {
-> rpl::producer<ResolvedTheme> { -> rpl::producer<ResolvedTheme> {
return rpl::combine( return rpl::combine(
MaybeChatThemeDataValueFromPeer(peer), MaybeChatThemeDataValueFromPeer(peer),
PeerWallPaperValue(peer),
Theme::IsThemeDarkValue() | rpl::distinct_until_changed() Theme::IsThemeDarkValue() | rpl::distinct_until_changed()
) | rpl::map([](std::optional<Data::CloudTheme> theme, bool night) { ) | rpl::map([](
return ResolvedTheme{ std::move(theme), night }; std::optional<Data::CloudTheme> theme,
std::optional<ResolvedPaper> paper,
bool night) {
return ResolvedTheme{ std::move(theme), std::move(paper), night };
}); });
} }
@ -338,13 +382,23 @@ auto ChatThemeValueFromPeer(
peer peer
) | rpl::map([=](ResolvedTheme resolved) ) | rpl::map([=](ResolvedTheme resolved)
-> rpl::producer<std::shared_ptr<Ui::ChatTheme>> { -> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
return resolved.theme if (!resolved.theme && !resolved.paper) {
? controller->cachedChatThemeValue( return rpl::single(controller->defaultChatTheme());
*resolved.theme, }
(resolved.dark const auto theme = resolved.theme.value_or(Data::CloudTheme());
? Data::CloudThemeType::Dark const auto paper = resolved.paper
: Data::CloudThemeType::Light)) ? resolved.paper->paper
: rpl::single(controller->defaultChatTheme()); : Data::WallPaper(0);
const auto type = resolved.dark
? Data::CloudThemeType::Dark
: Data::CloudThemeType::Light;
if (paper.document()
&& resolved.paper->media
&& !resolved.paper->media->loaded()
&& !controller->chatThemeAlreadyCached(theme, paper, type)) {
return rpl::single(controller->defaultChatTheme());
}
return controller->cachedChatThemeValue(theme, paper, type);
}) | rpl::flatten_latest( }) | rpl::flatten_latest(
) | rpl::distinct_until_changed(); ) | rpl::distinct_until_changed();
@ -354,7 +408,8 @@ auto ChatThemeValueFromPeer(
) | rpl::map([=]( ) | rpl::map([=](
std::shared_ptr<Ui::ChatTheme> &&cloud, std::shared_ptr<Ui::ChatTheme> &&cloud,
PeerThemeOverride &&overriden) { PeerThemeOverride &&overriden) {
return (overriden.peer == peer.get()) return (overriden.peer == peer.get()
&& Ui::Emoji::Find(peer->themeEmoji()) != overriden.emoji)
? std::move(overriden.theme) ? std::move(overriden.theme)
: std::move(cloud); : std::move(cloud);
}); });

View file

@ -97,9 +97,15 @@ constexpr auto kMaxChatEntryHistorySize = 50;
constexpr auto kDayBaseFile = ":/gui/day-custom-base.tdesktop-theme"_cs; constexpr auto kDayBaseFile = ":/gui/day-custom-base.tdesktop-theme"_cs;
constexpr auto kNightBaseFile = ":/gui/night-custom-base.tdesktop-theme"_cs; constexpr auto kNightBaseFile = ":/gui/night-custom-base.tdesktop-theme"_cs;
[[nodiscard]] Fn<void(style::palette&)> PrepareDefaultPaletteCallback() {
return [=](style::palette &palette) {
palette.reset();
};
}
[[nodiscard]] Fn<void(style::palette&)> PreparePaletteCallback( [[nodiscard]] Fn<void(style::palette&)> PreparePaletteCallback(
bool dark, bool dark,
std::optional<QColor> accent) { std::optional<QColor> accent) {
return [=](style::palette &palette) { return [=](style::palette &palette) {
using namespace Theme; using namespace Theme;
const auto &embedded = EmbeddedThemes(); const auto &embedded = EmbeddedThemes();
@ -727,10 +733,23 @@ void SessionNavigation::showPollResults(
showSection(std::make_shared<Info::Memento>(poll, contextId), params); showSection(std::make_shared<Info::Memento>(poll, contextId), params);
} }
struct SessionController::CachedThemeKey {
Ui::ChatThemeKey theme;
QString paper;
friend inline auto operator<=>(
const CachedThemeKey&,
const CachedThemeKey&) = default;
[[nodiscard]] explicit operator bool() const {
return theme || !paper.isEmpty();
}
};
struct SessionController::CachedTheme { struct SessionController::CachedTheme {
std::weak_ptr<Ui::ChatTheme> theme; std::weak_ptr<Ui::ChatTheme> theme;
std::shared_ptr<Data::DocumentMedia> media; std::shared_ptr<Data::DocumentMedia> media;
Data::WallPaper paper; Data::WallPaper paper;
bool basedOnDark = false;
bool caching = false; bool caching = false;
rpl::lifetime lifetime; rpl::lifetime lifetime;
}; };
@ -2052,19 +2071,29 @@ void SessionController::openDocument(
auto SessionController::cachedChatThemeValue( auto SessionController::cachedChatThemeValue(
const Data::CloudTheme &data, const Data::CloudTheme &data,
const Data::WallPaper &paper,
Data::CloudThemeType type) Data::CloudThemeType type)
-> rpl::producer<std::shared_ptr<Ui::ChatTheme>> { -> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
const auto key = Ui::ChatThemeKey{ const auto themeKey = Ui::ChatThemeKey{
data.id, data.id,
(type == Data::CloudThemeType::Dark), (type == Data::CloudThemeType::Dark),
}; };
const auto settings = data.settings.find(type); if (!themeKey && paper.isNull()) {
if (!key
|| (settings == end(data.settings))
|| !settings->second.paper
|| settings->second.paper->backgroundColors().empty()) {
return rpl::single(_defaultChatTheme); return rpl::single(_defaultChatTheme);
} }
const auto settings = data.settings.find(type);
if (!data.id && settings == end(data.settings)) {
return rpl::single(_defaultChatTheme);
}
if (paper.isNull()
&& (!settings->second.paper
|| settings->second.paper->backgroundColors().empty())) {
return rpl::single(_defaultChatTheme);
}
const auto key = CachedThemeKey{
themeKey,
!paper.isNull() ? paper.key() : settings->second.paper->key(),
};
const auto i = _customChatThemes.find(key); const auto i = _customChatThemes.find(key);
if (i != end(_customChatThemes)) { if (i != end(_customChatThemes)) {
if (auto strong = i->second.theme.lock()) { if (auto strong = i->second.theme.lock()) {
@ -2073,7 +2102,7 @@ auto SessionController::cachedChatThemeValue(
} }
} }
if (i == end(_customChatThemes) || !i->second.caching) { if (i == end(_customChatThemes) || !i->second.caching) {
cacheChatTheme(data, type); cacheChatTheme(key, data, paper, type);
} }
const auto limit = Data::CloudThemes::TestingColors() ? (1 << 20) : 1; const auto limit = Data::CloudThemes::TestingColors() ? (1 << 20) : 1;
using namespace rpl::mappers; using namespace rpl::mappers;
@ -2081,7 +2110,8 @@ auto SessionController::cachedChatThemeValue(
_defaultChatTheme _defaultChatTheme
) | rpl::then(_cachedThemesStream.events( ) | rpl::then(_cachedThemesStream.events(
) | rpl::filter([=](const std::shared_ptr<Ui::ChatTheme> &theme) { ) | rpl::filter([=](const std::shared_ptr<Ui::ChatTheme> &theme) {
if (theme->key() != key) { if (theme->key() != key.theme
|| theme->background().key != key.paper) {
return false; return false;
} }
pushLastUsedChatTheme(theme); pushLastUsedChatTheme(theme);
@ -2089,6 +2119,24 @@ auto SessionController::cachedChatThemeValue(
}) | rpl::take(limit)); }) | rpl::take(limit));
} }
bool SessionController::chatThemeAlreadyCached(
const Data::CloudTheme &data,
const Data::WallPaper &paper,
Data::CloudThemeType type) {
Expects(paper.document() != nullptr);
const auto key = CachedThemeKey{
Ui::ChatThemeKey{
data.id,
(type == Data::CloudThemeType::Dark),
},
paper.key(),
};
const auto i = _customChatThemes.find(key);
return (i != end(_customChatThemes))
&& (i->second.theme.lock() != nullptr);
}
void SessionController::pushLastUsedChatTheme( void SessionController::pushLastUsedChatTheme(
const std::shared_ptr<Ui::ChatTheme> &theme) { const std::shared_ptr<Ui::ChatTheme> &theme) {
const auto i = ranges::find(_lastUsedCustomChatThemes, theme); const auto i = ranges::find(_lastUsedCustomChatThemes, theme);
@ -2124,10 +2172,12 @@ void SessionController::clearCachedChatThemes() {
void SessionController::overridePeerTheme( void SessionController::overridePeerTheme(
not_null<PeerData*> peer, not_null<PeerData*> peer,
std::shared_ptr<Ui::ChatTheme> theme) { std::shared_ptr<Ui::ChatTheme> theme,
EmojiPtr emoji) {
_peerThemeOverride = PeerThemeOverride{ _peerThemeOverride = PeerThemeOverride{
peer, peer,
theme ? theme : _defaultChatTheme, theme ? theme : _defaultChatTheme,
emoji,
}; };
} }
@ -2154,25 +2204,28 @@ void SessionController::pushDefaultChatBackground() {
} }
void SessionController::cacheChatTheme( void SessionController::cacheChatTheme(
CachedThemeKey key,
const Data::CloudTheme &data, const Data::CloudTheme &data,
const Data::WallPaper &paper,
Data::CloudThemeType type) { Data::CloudThemeType type) {
Expects(data.id != 0); Expects(data.id != 0 || !paper.isNull());
const auto dark = (type == Data::CloudThemeType::Dark); const auto dark = (type == Data::CloudThemeType::Dark);
const auto key = Ui::ChatThemeKey{ data.id, dark };
const auto i = data.settings.find(type); const auto i = data.settings.find(type);
Assert(i != end(data.settings)); Assert((!data.id || (i != end(data.settings)))
const auto &paper = i->second.paper; && (!paper.isNull()
Assert(paper.has_value()); || (i->second.paper.has_value()
Assert(!paper->backgroundColors().empty()); && !i->second.paper->backgroundColors().empty())));
const auto document = paper->document(); const auto &use = !paper.isNull() ? paper : *i->second.paper;
const auto document = use.document();
const auto media = document ? document->createMediaView() : nullptr; const auto media = document ? document->createMediaView() : nullptr;
paper->loadDocument(); use.loadDocument();
auto &theme = [&]() -> CachedTheme& { auto &theme = [&]() -> CachedTheme& {
const auto i = _customChatThemes.find(key); const auto i = _customChatThemes.find(key);
if (i != end(_customChatThemes)) { if (i != end(_customChatThemes)) {
i->second.media = media; i->second.media = media;
i->second.paper = *paper; i->second.paper = use;
i->second.basedOnDark = dark;
i->second.caching = true; i->second.caching = true;
return i->second; return i->second;
} }
@ -2180,15 +2233,16 @@ void SessionController::cacheChatTheme(
key, key,
CachedTheme{ CachedTheme{
.media = media, .media = media,
.paper = *paper, .paper = use,
.basedOnDark = dark,
.caching = true, .caching = true,
}).first->second; }).first->second;
}(); }();
auto descriptor = Ui::ChatThemeDescriptor{ auto descriptor = Ui::ChatThemeDescriptor{
.key = key, .key = key.theme,
.preparePalette = PreparePaletteCallback( .preparePalette = (data.id
dark, ? PreparePaletteCallback(dark, i->second.accentColor)
i->second.accentColor), : PrepareDefaultPaletteCallback()),
.backgroundData = backgroundData(theme), .backgroundData = backgroundData(theme),
.bubblesData = PrepareBubblesData(data, type), .bubblesData = PrepareBubblesData(data, type),
.basedOnDark = dark, .basedOnDark = dark,
@ -2215,7 +2269,10 @@ void SessionController::cacheChatThemeDone(
std::shared_ptr<Ui::ChatTheme> result) { std::shared_ptr<Ui::ChatTheme> result) {
Expects(result != nullptr); Expects(result != nullptr);
const auto key = result->key(); const auto key = CachedThemeKey{
result->key(),
result->background().key,
};
const auto i = _customChatThemes.find(key); const auto i = _customChatThemes.find(key);
if (i == end(_customChatThemes)) { if (i == end(_customChatThemes)) {
return; return;
@ -2257,7 +2314,8 @@ void SessionController::updateCustomThemeBackground(CachedTheme &theme) {
=, =,
result = Ui::PrepareBackgroundImage(data) result = Ui::PrepareBackgroundImage(data)
]() mutable { ]() mutable {
const auto i = _customChatThemes.find(key); const auto cacheKey = CachedThemeKey{ key, result.key };
const auto i = _customChatThemes.find(cacheKey);
if (i != end(_customChatThemes)) { if (i != end(_customChatThemes)) {
if (const auto strong = i->second.theme.lock()) { if (const auto strong = i->second.theme.lock()) {
strong->updateBackgroundImageFrom(std::move(result)); strong->updateBackgroundImageFrom(std::move(result));
@ -2280,14 +2338,20 @@ Ui::ChatThemeBackgroundData SessionController::backgroundData(
const auto patternOpacity = paper.patternOpacity(); const auto patternOpacity = paper.patternOpacity();
const auto isBlurred = paper.isBlurred(); const auto isBlurred = paper.isBlurred();
const auto gradientRotation = paper.gradientRotation(); const auto gradientRotation = paper.gradientRotation();
const auto darkModeDimming = isPattern
? 100
: std::clamp(paper.patternIntensity(), 0, 100);
return { return {
.key = paper.key(),
.path = paperPath, .path = paperPath,
.bytes = paperBytes, .bytes = paperBytes,
.gzipSvg = gzipSvg, .gzipSvg = gzipSvg,
.colors = colors, .colors = colors,
.isPattern = isPattern, .isPattern = isPattern,
.patternOpacity = patternOpacity, .patternOpacity = patternOpacity,
.darkModeDimming = darkModeDimming,
.isBlurred = isBlurred, .isBlurred = isBlurred,
.forDarkMode = theme.basedOnDark,
.generateGradient = generateGradient, .generateGradient = generateGradient,
.gradientRotation = gradientRotation, .gradientRotation = gradientRotation,
}; };

View file

@ -71,6 +71,7 @@ enum class CloudThemeType;
class Thread; class Thread;
class Forum; class Forum;
class ForumTopic; class ForumTopic;
class WallPaper;
} // namespace Data } // namespace Data
namespace HistoryView::Reactions { namespace HistoryView::Reactions {
@ -108,6 +109,7 @@ enum class ResolveType {
struct PeerThemeOverride { struct PeerThemeOverride {
PeerData *peer = nullptr; PeerData *peer = nullptr;
std::shared_ptr<Ui::ChatTheme> theme; std::shared_ptr<Ui::ChatTheme> theme;
EmojiPtr emoji = nullptr;
}; };
bool operator==(const PeerThemeOverride &a, const PeerThemeOverride &b); bool operator==(const PeerThemeOverride &a, const PeerThemeOverride &b);
bool operator!=(const PeerThemeOverride &a, const PeerThemeOverride &b); bool operator!=(const PeerThemeOverride &a, const PeerThemeOverride &b);
@ -538,8 +540,13 @@ public:
} }
[[nodiscard]] auto cachedChatThemeValue( [[nodiscard]] auto cachedChatThemeValue(
const Data::CloudTheme &data, const Data::CloudTheme &data,
const Data::WallPaper &paper,
Data::CloudThemeType type) Data::CloudThemeType type)
-> rpl::producer<std::shared_ptr<Ui::ChatTheme>>; -> rpl::producer<std::shared_ptr<Ui::ChatTheme>>;
[[nodiscard]] bool chatThemeAlreadyCached(
const Data::CloudTheme &data,
const Data::WallPaper &paper,
Data::CloudThemeType type);
void setChatStyleTheme(const std::shared_ptr<Ui::ChatTheme> &theme); void setChatStyleTheme(const std::shared_ptr<Ui::ChatTheme> &theme);
void clearCachedChatThemes(); void clearCachedChatThemes();
void pushLastUsedChatTheme(const std::shared_ptr<Ui::ChatTheme> &theme); void pushLastUsedChatTheme(const std::shared_ptr<Ui::ChatTheme> &theme);
@ -547,7 +554,8 @@ public:
void overridePeerTheme( void overridePeerTheme(
not_null<PeerData*> peer, not_null<PeerData*> peer,
std::shared_ptr<Ui::ChatTheme> theme); std::shared_ptr<Ui::ChatTheme> theme,
EmojiPtr emoji);
void clearPeerThemeOverride(not_null<PeerData*> peer); void clearPeerThemeOverride(not_null<PeerData*> peer);
[[nodiscard]] auto peerThemeOverrideValue() const [[nodiscard]] auto peerThemeOverrideValue() const
-> rpl::producer<PeerThemeOverride> { -> rpl::producer<PeerThemeOverride> {
@ -586,6 +594,7 @@ public:
} }
private: private:
struct CachedThemeKey;
struct CachedTheme; struct CachedTheme;
void init(); void init();
@ -616,7 +625,9 @@ private:
void pushDefaultChatBackground(); void pushDefaultChatBackground();
void cacheChatTheme( void cacheChatTheme(
CachedThemeKey key,
const Data::CloudTheme &data, const Data::CloudTheme &data,
const Data::WallPaper &paper,
Data::CloudThemeType type); Data::CloudThemeType type);
void cacheChatThemeDone(std::shared_ptr<Ui::ChatTheme> result); void cacheChatThemeDone(std::shared_ptr<Ui::ChatTheme> result);
void updateCustomThemeBackground(CachedTheme &theme); void updateCustomThemeBackground(CachedTheme &theme);
@ -668,7 +679,7 @@ private:
rpl::event_stream<> _filtersMenuChanged; rpl::event_stream<> _filtersMenuChanged;
std::shared_ptr<Ui::ChatTheme> _defaultChatTheme; std::shared_ptr<Ui::ChatTheme> _defaultChatTheme;
base::flat_map<Ui::ChatThemeKey, CachedTheme> _customChatThemes; base::flat_map<CachedThemeKey, CachedTheme> _customChatThemes;
rpl::event_stream<std::shared_ptr<Ui::ChatTheme>> _cachedThemesStream; rpl::event_stream<std::shared_ptr<Ui::ChatTheme>> _cachedThemesStream;
const std::unique_ptr<Ui::ChatStyle> _chatStyle; const std::unique_ptr<Ui::ChatStyle> _chatStyle;
std::weak_ptr<Ui::ChatTheme> _chatStyleTheme; std::weak_ptr<Ui::ChatTheme> _chatStyleTheme;