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) {
[[maybe_unused]] auto value = theme->settings.contains(type)
? controller->cachedChatThemeValue(*theme, type)
? controller->cachedChatThemeValue(
*theme,
Data::WallPaper(0),
type)
: nullptr;
};
recache(Data::CloudThemeType::Dark);

View File

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

View File

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

View File

@ -1093,6 +1093,22 @@ const QString &PeerData::themeEmoji() const {
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) {
const auto status = is
? BlockStatus::Blocked

View File

@ -37,6 +37,7 @@ class ForumTopic;
class Session;
class GroupCall;
struct ReactionId;
class WallPaper;
[[nodiscard]] int PeerColorIndex(PeerId peerId);
@ -403,6 +404,9 @@ public:
void setThemeEmoji(const QString &emoticon);
[[nodiscard]] const QString &themeEmoji() const;
void setWallPaper(std::optional<Data::WallPaper> paper);
[[nodiscard]] const Data::WallPaper *wallPaper() const;
const PeerId id;
MTPinputPeer input = MTP_inputPeerEmpty();
@ -457,6 +461,7 @@ private:
QString _about;
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_emoji_statuses.h"
#include "data/data_user_names.h"
#include "data/data_wall_paper.h"
#include "data/notify/data_notify_settings.h"
#include "api/api_peer_photo.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();
}

View File

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

View File

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

View File

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

View File

@ -2213,6 +2213,13 @@ bool HistoryItem::hasDirectLink() const {
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 {
return FullMsgId(_history->peer->id, id);
}

View File

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

View File

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

View File

@ -88,6 +88,7 @@ private:
struct Button {
void drawBg(QPainter &p) const;
void toggleRipple(bool pressed);
[[nodiscard]] bool empty() const;
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_wall_paper.h"
#include "base/qthelp_url.h"
#include "core/click_handler_types.h"
#include "core/local_url_handlers.h"
#include "lang/lang_keys.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/painter.h"
#include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
namespace HistoryView {
@ -433,15 +435,21 @@ QString ThemeDocumentBox::subtitle() {
}
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() {
const auto out = _parent->data()->out();
const auto to = _parent->history()->peer;
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
//const auto my = context.other.value<ClickHandlerContext>();
//if (const auto controller = my.sessionWindow.get()) {
//}
const auto my = context.other.value<ClickHandlerContext>();
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
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.tile == b.tile)
&& (a.patternOpacity == b.patternOpacity);
@ -222,9 +223,14 @@ void ChatTheme::adjustPalette(const ChatThemeDescriptor &descriptor) {
if (overrideOutBg) {
set(p.msgOutBg(), descriptor.bubblesData.colors.front());
}
const auto &background = descriptor.backgroundData.colors;
if (!background.empty()) {
const auto average = CountAverageColor(background);
const auto &data = descriptor.backgroundData;
const auto &background = data.colors;
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.msgServiceBgSelected(), average);
adjust(p.historyScrollBg(), average);
@ -407,6 +413,7 @@ void ChatTheme::setBackground(ChatThemeBackground &&background) {
}
void ChatTheme::updateBackgroundImageFrom(ChatThemeBackground &&background) {
_mutableBackground.key = background.key;
_mutableBackground.prepared = std::move(background.prepared);
_mutableBackground.preparedForTiled = std::move(
background.preparedForTiled);
@ -1028,6 +1035,16 @@ ChatThemeBackground PrepareBackgroundImage(
} else if (data.colors.empty()) {
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)
? CalculateImageMonoColor(prepared)
: std::nullopt;
@ -1038,6 +1055,7 @@ ChatThemeBackground PrepareBackgroundImage(
? Ui::GenerateDitheredGradient(data.colors, data.gradientRotation)
: QImage();
return ChatThemeBackground{
.key = data.key,
.prepared = prepared,
.preparedForTiled = PrepareImageForTiled(prepared),
.gradientForFill = std::move(gradientForFill),

View File

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

View File

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

View File

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer.h"
#include "data/data_user.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_changes.h"
#include "data/data_session.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(
not_null<PeerData*> peer)
-> rpl::producer<std::optional<Data::CloudTheme>> {
@ -58,6 +97,7 @@ namespace {
struct ResolvedTheme {
std::optional<Data::CloudTheme> theme;
std::optional<ResolvedPaper> paper;
bool dark = false;
};
@ -66,9 +106,13 @@ struct ResolvedTheme {
-> rpl::producer<ResolvedTheme> {
return rpl::combine(
MaybeChatThemeDataValueFromPeer(peer),
PeerWallPaperValue(peer),
Theme::IsThemeDarkValue() | rpl::distinct_until_changed()
) | rpl::map([](std::optional<Data::CloudTheme> theme, bool night) {
return ResolvedTheme{ std::move(theme), night };
) | rpl::map([](
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
) | rpl::map([=](ResolvedTheme resolved)
-> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
return resolved.theme
? controller->cachedChatThemeValue(
*resolved.theme,
(resolved.dark
? Data::CloudThemeType::Dark
: Data::CloudThemeType::Light))
: rpl::single(controller->defaultChatTheme());
if (!resolved.theme && !resolved.paper) {
return rpl::single(controller->defaultChatTheme());
}
const auto theme = resolved.theme.value_or(Data::CloudTheme());
const auto paper = resolved.paper
? resolved.paper->paper
: 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::distinct_until_changed();
@ -354,7 +408,8 @@ auto ChatThemeValueFromPeer(
) | rpl::map([=](
std::shared_ptr<Ui::ChatTheme> &&cloud,
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(cloud);
});

View File

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

View File

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