diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 4c09794a8..fe9b26210 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -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); diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index 7b75478e9..e5bbc48e4 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -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; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index a1d8539e6..2adfe69e4 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -794,7 +794,7 @@ QString DocumentData::loadingFilePath() const { bool DocumentData::displayLoading() const { return loading() - ? (!_loader->loadingLocal() || !_loader->autoLoading()) + ? !_loader->loadingLocal() : (uploading() && !waitingForAlbum()); } diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 560736b46..42c548438 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -1093,6 +1093,22 @@ const QString &PeerData::themeEmoji() const { return _themeEmoticon; } +void PeerData::setWallPaper(std::optional paper) { + if (!paper && !_wallPaper) { + return; + } else if (paper && _wallPaper && _wallPaper->equals(*paper)) { + return; + } + _wallPaper = paper + ? std::make_unique(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 diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 1940e573b..17b19bf63 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -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 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 _wallPaper; }; diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index f474f701e..a5c6de084 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -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 user, const MTPDuserFull &update) { } } + if (const auto paper = update.vwallpaper()) { + user->setWallPaper( + Data::WallPaper::Create(&user->session(), *paper)); + } else { + user->setWallPaper({}); + } + user->fullUpdated(); } diff --git a/Telegram/SourceFiles/data/data_wall_paper.cpp b/Telegram/SourceFiles/data/data_wall_paper.cpp index b2313f169..468c05f87 100644 --- a/Telegram/SourceFiles/data/data_wall_paper.cpp +++ b/Telegram/SourceFiles/data/data_wall_paper.cpp @@ -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 WallPaper::backgroundColors() const { return _backgroundColors; } @@ -251,34 +261,54 @@ bool WallPaper::hasShareUrl() const { return !_slug.isEmpty(); } -QString WallPaper::shareUrl(not_null 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 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 { diff --git a/Telegram/SourceFiles/data/data_wall_paper.h b/Telegram/SourceFiles/data/data_wall_paper.h index df38478ee..fb98eb9de 100644 --- a/Telegram/SourceFiles/data/data_wall_paper.h +++ b/Telegram/SourceFiles/data/data_wall_paper.h @@ -42,7 +42,11 @@ public: void setLocalImageAsThumbnail(std::shared_ptr image); + [[nodiscard]] bool equals(const WallPaper &paper) const; + [[nodiscard]] WallPaperId id() const; + [[nodiscard]] bool isNull() const; + [[nodiscard]] QString key() const; [[nodiscard]] const std::vector 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; diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 5b965667b..a3f2cc87d 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1264,6 +1264,9 @@ void History::newItemAdded(not_null item) { if (!item->unread(this)) { outboxRead(item); } + if (item->changesWallPaper()) { + peer->updateFullForced(); + } } else { if (item->unread(this)) { if (unreadCountKnown()) { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index d66e22e98..8779cd41f 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -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(); +} + FullMsgId HistoryItem::fullId() const { return FullMsgId(_history->peer->id, id); } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 629cc7267..e6dcac508 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -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; diff --git a/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp b/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp index 149998353..e129d28f5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp @@ -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); diff --git a/Telegram/SourceFiles/history/view/media/history_view_service_box.h b/Telegram/SourceFiles/history/view/media/history_view_service_box.h index 2362304c5..a986b01a8 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_service_box.h +++ b/Telegram/SourceFiles/history/view/media/history_view_service_box.h @@ -88,6 +88,7 @@ private: struct Button { void drawBg(QPainter &p) const; void toggleRipple(bool pressed); + [[nodiscard]] bool empty() const; Fn repaint; diff --git a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp index ec645442b..76024cea8 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp @@ -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([=](ClickContext context) { - //const auto my = context.other.value(); - //if (const auto controller = my.sessionWindow.get()) { - //} + const auto my = context.other.value(); + if (const auto controller = my.sessionWindow.get()) { + if (out) { + controller->toggleChooseChatTheme(to); + } + } }); } diff --git a/Telegram/SourceFiles/ui/chat/chat_theme.cpp b/Telegram/SourceFiles/ui/chat/chat_theme.cpp index a8a724b7f..856b75b4a 100644 --- a/Telegram/SourceFiles/ui/chat/chat_theme.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_theme.cpp @@ -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), diff --git a/Telegram/SourceFiles/ui/chat/chat_theme.h b/Telegram/SourceFiles/ui/chat/chat_theme.h index a48102cd5..91c2599e7 100644 --- a/Telegram/SourceFiles/ui/chat/chat_theme.h +++ b/Telegram/SourceFiles/ui/chat/chat_theme.h @@ -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 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; diff --git a/Telegram/SourceFiles/ui/chat/choose_theme_controller.cpp b/Telegram/SourceFiles/ui/chat/choose_theme_controller.cpp index 2f62f969f..9f64c4963 100644 --- a/Telegram/SourceFiles/ui/chat/choose_theme_controller.cpp +++ b/Telegram/SourceFiles/ui/chat/choose_theme_controller.cpp @@ -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 &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(); diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index a120be093..570dd7aaf 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -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 media; +}; + +[[nodiscard]] rpl::producer> PeerWallPaperValue( + not_null peer) { + return peer->session().changes().peerFlagsValue( + peer, + Data::PeerUpdate::Flag::ChatWallPaper + ) | rpl::map([=]() -> rpl::producer> { + const auto paper = peer->wallPaper(); + const auto single = [](std::optional 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(value) + )); + }) | rpl::flatten_latest(); +} + [[nodiscard]] auto MaybeChatThemeDataValueFromPeer( not_null peer) -> rpl::producer> { @@ -58,6 +97,7 @@ namespace { struct ResolvedTheme { std::optional theme; + std::optional paper; bool dark = false; }; @@ -66,9 +106,13 @@ struct ResolvedTheme { -> rpl::producer { return rpl::combine( MaybeChatThemeDataValueFromPeer(peer), + PeerWallPaperValue(peer), Theme::IsThemeDarkValue() | rpl::distinct_until_changed() - ) | rpl::map([](std::optional theme, bool night) { - return ResolvedTheme{ std::move(theme), night }; + ) | rpl::map([]( + std::optional theme, + std::optional 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> { - 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 &&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); }); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 3d8a1eeeb..a5645879a 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -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 PrepareDefaultPaletteCallback() { + return [=](style::palette &palette) { + palette.reset(); + }; +} + [[nodiscard]] Fn PreparePaletteCallback( - bool dark, - std::optional accent) { + bool dark, + std::optional accent) { return [=](style::palette &palette) { using namespace Theme; const auto &embedded = EmbeddedThemes(); @@ -727,10 +733,23 @@ void SessionNavigation::showPollResults( showSection(std::make_shared(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 theme; std::shared_ptr 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> { - 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 &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 &theme) { const auto i = ranges::find(_lastUsedCustomChatThemes, theme); @@ -2124,10 +2172,12 @@ void SessionController::clearCachedChatThemes() { void SessionController::overridePeerTheme( not_null peer, - std::shared_ptr theme) { + std::shared_ptr 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 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, }; diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 5500fddf0..01b9a8761 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -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 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>; + [[nodiscard]] bool chatThemeAlreadyCached( + const Data::CloudTheme &data, + const Data::WallPaper &paper, + Data::CloudThemeType type); void setChatStyleTheme(const std::shared_ptr &theme); void clearCachedChatThemes(); void pushLastUsedChatTheme(const std::shared_ptr &theme); @@ -547,7 +554,8 @@ public: void overridePeerTheme( not_null peer, - std::shared_ptr theme); + std::shared_ptr theme, + EmojiPtr emoji); void clearPeerThemeOverride(not_null peer); [[nodiscard]] auto peerThemeOverrideValue() const -> rpl::producer { @@ -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 result); void updateCustomThemeBackground(CachedTheme &theme); @@ -668,7 +679,7 @@ private: rpl::event_stream<> _filtersMenuChanged; std::shared_ptr _defaultChatTheme; - base::flat_map _customChatThemes; + base::flat_map _customChatThemes; rpl::event_stream> _cachedThemesStream; const std::unique_ptr _chatStyle; std::weak_ptr _chatStyleTheme;