diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index aab6d6862..6fbf8a573 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -729,7 +729,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_manage_enabled_dictionary" = "Dictionary is enabled"; "lng_settings_manage_remove_dictionary" = "Remove Dictionary"; -"lng_backgrounds_header" = "Choose your new chat background"; +"lng_backgrounds_header" = "Choose Wallpaper"; "lng_theme_sure_keep" = "Keep this theme?"; "lng_theme_reverting#one" = "Reverting to the old theme in {count} second."; "lng_theme_reverting#other" = "Reverting to the old theme in {count} seconds."; diff --git a/Telegram/SourceFiles/boxes/background_box.cpp b/Telegram/SourceFiles/boxes/background_box.cpp index a90f5a6ac..c5d15e377 100644 --- a/Telegram/SourceFiles/boxes/background_box.cpp +++ b/Telegram/SourceFiles/boxes/background_box.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "apiwrap.h" #include "mtproto/sender.h" +#include "data/data_peer.h" #include "data/data_session.h" #include "data/data_file_origin.h" #include "data/data_document.h" @@ -61,11 +62,15 @@ class BackgroundBox::Inner final : public Ui::RpWidget { public: Inner( QWidget *parent, - not_null session); + not_null session, + PeerData *forPeer); ~Inner(); - rpl::producer chooseEvents() const; - rpl::producer removeRequests() const; + [[nodiscard]] rpl::producer chooseEvents() const; + [[nodiscard]] rpl::producer removeRequests() const; + + [[nodiscard]] auto resolveResetCustomPaper() const + ->std::optional; void removePaper(const Data::WallPaper &data); @@ -109,6 +114,7 @@ private: void resizeToContentAndPreload(); void updatePapers(); void requestPapers(); + void pushResetCustomPaper(); void sortPapers(); void paintPaper( QPainter &p, @@ -118,9 +124,13 @@ private: void validatePaperThumbnail(const Paper &paper) const; const not_null _session; + PeerData * const _forPeer = nullptr; + MTP::Sender _api; std::vector _papers; + uint64 _currentId = 0; + uint64 _insertedResetId = 0; Selection _over; Selection _overDown; @@ -133,8 +143,10 @@ private: BackgroundBox::BackgroundBox( QWidget*, - not_null controller) -: _controller(controller) { + not_null controller, + PeerData *forPeer) +: _controller(controller) +, _forPeer(forPeer) { } void BackgroundBox::prepare() { @@ -145,14 +157,12 @@ void BackgroundBox::prepare() { setDimensions(st::boxWideWidth, st::boxMaxListHeight); _inner = setInnerWidget( - object_ptr(this, &_controller->session()), + object_ptr(this, &_controller->session(), _forPeer), st::backgroundScroll); _inner->chooseEvents( ) | rpl::start_with_next([=](const Data::WallPaper &paper) { - _controller->show( - Box(_controller, paper), - Ui::LayerOption::KeepOther); + chosen(paper); }, _inner->lifetime()); _inner->removeRequests( @@ -161,6 +171,74 @@ void BackgroundBox::prepare() { }, _inner->lifetime()); } +bool BackgroundBox::hasDefaultForPeer() const { + Expects(_forPeer != nullptr); + + const auto paper = _forPeer->wallPaper(); + if (!paper) { + return true; + } + const auto reset = _inner->resolveResetCustomPaper(); + Assert(reset.has_value()); + return (paper->id() == reset->id()); +} + +bool BackgroundBox::chosenDefaultForPeer( + const Data::WallPaper &paper) const { + if (!_forPeer) { + return false; + } + + const auto reset = _inner->resolveResetCustomPaper(); + Assert(reset.has_value()); + return (paper.id() == reset->id()); +} + +void BackgroundBox::chosen(const Data::WallPaper &paper) { + if (chosenDefaultForPeer(paper)) { + if (!hasDefaultForPeer()) { + const auto reset = crl::guard(this, [=](Fn close) { + resetForPeer(); + close(); + }); + _controller->show(Ui::MakeConfirmBox({ + .text = u"Are you sure you want to reset the wallpaper?"_q, + .confirmed = reset, + .confirmText = u"Reset"_q, + })); + } else { + closeBox(); + } + return; + } + _controller->show( + Box( + _controller, + paper, + BackgroundPreviewArgs{ _forPeer }), + Ui::LayerOption::KeepOther); +} + +void BackgroundBox::resetForPeer() { + const auto api = &_controller->session().api(); + using Flag = MTPmessages_SetChatWallPaper::Flag; + api->request(MTPmessages_SetChatWallPaper( + MTP_flags(0), + _forPeer->input, + MTPInputWallPaper(), + MTPWallPaperSettings(), + MTPint() + )).done([=](const MTPUpdates &result) { + api->applyUpdates(result); + }).send(); + + const auto weak = Ui::MakeWeak(this); + _forPeer->setWallPaper(std::nullopt); + if (weak) { + _controller->finishChatThemeEdit(_forPeer); + } +} + void BackgroundBox::removePaper(const Data::WallPaper &paper) { const auto session = &_controller->session(); const auto remove = [=, weak = Ui::MakeWeak(this)](Fn &&close) { @@ -186,17 +264,19 @@ void BackgroundBox::removePaper(const Data::WallPaper &paper) { BackgroundBox::Inner::Inner( QWidget *parent, - not_null session) + not_null session, + PeerData *forPeer) : RpWidget(parent) , _session(session) +, _forPeer(forPeer) , _api(&_session->mtp()) , _check(std::make_unique(st::overviewCheck, [=] { update(); })) { _check->setChecked(true, anim::type::instant); - if (_session->data().wallpapers().empty()) { - resize(st::boxWideWidth, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding); - } else { + resize(st::boxWideWidth, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding); + Window::Theme::IsNightModeValue( + ) | rpl::start_with_next([=] { updatePapers(); - } + }, lifetime()); requestPapers(); _session->downloaderTaskFinished( @@ -219,6 +299,7 @@ BackgroundBox::Inner::Inner( } }, lifetime()); + setMouseTracking(true); } @@ -232,27 +313,78 @@ void BackgroundBox::Inner::requestPapers() { }).send(); } +auto BackgroundBox::Inner::resolveResetCustomPaper() const +-> std::optional { + if (!_forPeer) { + return {}; + } + const auto nonCustom = Window::Theme::Background()->paper(); + const auto themeEmoji = _forPeer->themeEmoji(); + if (themeEmoji.isEmpty()) { + return nonCustom; + } + const auto &themes = _forPeer->owner().cloudThemes(); + const auto theme = themes.themeForEmoji(themeEmoji); + if (!theme) { + return nonCustom; + } + using Type = Data::CloudTheme::Type; + const auto dark = Window::Theme::IsNightMode(); + const auto i = theme->settings.find(dark ? Type::Dark : Type::Light); + if (i != end(theme->settings) && i->second.paper) { + return *i->second.paper; + } + return nonCustom; +} + +void BackgroundBox::Inner::pushResetCustomPaper() { + if (const auto reset = resolveResetCustomPaper()) { + _insertedResetId = reset->id(); + const auto j = ranges::find( + _papers, + _insertedResetId, + [](const Paper &paper) { return paper.data.id(); }); + if (j != end(_papers)) { + j->data = j->data.withParamsFrom(*reset); + } else { + _papers.insert(begin(_papers), Paper{ *reset }); + } + } +} + void BackgroundBox::Inner::sortPapers() { - const auto current = Window::Theme::Background()->id(); - const auto night = Window::Theme::IsNightMode(); + const auto currentCustom = _forPeer ? _forPeer->wallPaper() : nullptr; + _currentId = currentCustom + ? currentCustom->id() + : _insertedResetId + ? _insertedResetId + : Window::Theme::Background()->id(); + const auto dark = Window::Theme::IsNightMode(); ranges::stable_sort(_papers, std::greater<>(), [&](const Paper &paper) { const auto &data = paper.data; return std::make_tuple( - data.id() == current, - night ? data.isDark() : !data.isDark(), + _insertedResetId && (_insertedResetId == data.id()), + data.id() == _currentId, + dark ? data.isDark() : !data.isDark(), Data::IsDefaultWallPaper(data), !data.isDefault() && !Data::IsLegacy1DefaultWallPaper(data), Data::IsLegacy3DefaultWallPaper(data), Data::IsLegacy2DefaultWallPaper(data), Data::IsLegacy1DefaultWallPaper(data)); }); - if (!_papers.empty() && _papers.front().data.id() == current) { + if (!_papers.empty() + && _papers.front().data.id() == _currentId + && !currentCustom + && !_insertedResetId) { _papers.front().data = _papers.front().data.withParamsFrom( Window::Theme::Background()->paper()); } } void BackgroundBox::Inner::updatePapers() { + if (_session->data().wallpapers().empty()) { + return; + } _over = _overDown = Selection(); _papers = _session->data().wallpapers( @@ -261,6 +393,7 @@ void BackgroundBox::Inner::updatePapers() { }) | ranges::views::transform([](const Data::WallPaper &paper) { return Paper{ paper }; }) | ranges::to_vector; + pushResetCustomPaper(); sortPapers(); resizeToContentAndPreload(); } @@ -373,7 +506,7 @@ void BackgroundBox::Inner::paintPaper( } const auto over = !v::is_null(_overDown) ? _overDown : _over; - if (paper.data.id() == Window::Theme::Background()->id()) { + if (paper.data.id() == _currentId) { const auto checkLeft = x + st::backgroundSize.width() - st::overviewCheckSkip - st::overviewCheck.size; const auto checkTop = y + st::backgroundSize.height() - st::overviewCheckSkip - st::overviewCheck.size; _check->paint(p, checkLeft, checkTop, width()); @@ -415,14 +548,13 @@ void BackgroundBox::Inner::mouseMoveEvent(QMouseEvent *e) { - st::stickerPanDeleteIconBg.width(); const auto deleteBottom = row * (height + skip) + skip + st::stickerPanDeleteIconBg.height(); - const auto currentId = Window::Theme::Background()->id(); const auto inDelete = (x >= deleteLeft) && (y < deleteBottom) && Data::IsCloudWallPaper(data) && !Data::IsDefaultWallPaper(data) && !Data::IsLegacy2DefaultWallPaper(data) && !Data::IsLegacy3DefaultWallPaper(data) - && (currentId != data.id()); + && (_currentId != data.id()); return (result >= _papers.size()) ? Selection() : inDelete diff --git a/Telegram/SourceFiles/boxes/background_box.h b/Telegram/SourceFiles/boxes/background_box.h index e3d5994ae..0fc28c44f 100644 --- a/Telegram/SourceFiles/boxes/background_box.h +++ b/Telegram/SourceFiles/boxes/background_box.h @@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/abstract_box.h" +class PeerData; + namespace Window { class SessionController; } // namespace Window @@ -19,7 +21,10 @@ class WallPaper; class BackgroundBox : public Ui::BoxContent { public: - BackgroundBox(QWidget*, not_null controller); + BackgroundBox( + QWidget*, + not_null controller, + PeerData *forPeer = nullptr); protected: void prepare() override; @@ -27,10 +32,16 @@ protected: private: class Inner; + void chosen(const Data::WallPaper &paper); + [[nodiscard]] bool hasDefaultForPeer() const; + [[nodiscard]] bool chosenDefaultForPeer( + const Data::WallPaper &paper) const; void removePaper(const Data::WallPaper &paper); + void resetForPeer(); const not_null _controller; QPointer _inner; + PeerData *_forPeer = nullptr; }; diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index 5040f870f..d35d1844b 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -164,23 +164,17 @@ BackgroundPreviewBox::BackgroundPreviewBox( , _chatStyle(std::make_unique()) , _serviceHistory(_controller->session().data().history( PeerData::kServiceNotificationsId)) -, _service((_forPeer && !_fromMessageId) - ? GenerateServiceItem( - delegate(), - _serviceHistory, - tr::lng_background_other_info(tr::now, lt_user, _forPeer->shortName()), - false) - : nullptr) +, _service(nullptr) , _text1(GenerateTextItem( delegate(), - _controller->session().data().history(PeerData::kServiceNotificationsId), + _serviceHistory, (_forPeer ? tr::lng_background_apply1(tr::now) : tr::lng_background_text1(tr::now)), false)) , _text2(GenerateTextItem( delegate(), - _controller->session().data().history(PeerData::kServiceNotificationsId), + _serviceHistory, (_forPeer ? tr::lng_background_apply2(tr::now) : tr::lng_background_text2(tr::now)), @@ -244,11 +238,7 @@ void BackgroundPreviewBox::prepare() { setScaledFromThumb(); checkLoadedDocument(); - if (_service) { - _service->initDimensions(); - _service->resizeGetHeight(st::boxWideWidth); - } - _text1->setDisplayDate(!_service); + _text1->setDisplayDate(false); _text1->initDimensions(); _text1->resizeGetHeight(st::boxWideWidth); _text2->initDimensions(); @@ -285,12 +275,15 @@ void BackgroundPreviewBox::createBlurCheckbox() { } void BackgroundPreviewBox::apply() { + const auto weak = Ui::MakeWeak(this); if (_forPeer) { applyForPeer(); } else { applyForEveryone(); } - closeBox(); + if (weak) { + closeBox(); + } } void BackgroundPreviewBox::applyForPeer() { @@ -311,6 +304,7 @@ void BackgroundPreviewBox::applyForPeer() { }).send(); _forPeer->setWallPaper(_paper); + _controller->finishChatThemeEdit(_forPeer); } void BackgroundPreviewBox::applyForEveryone() { @@ -435,8 +429,6 @@ void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) { if (_service) { _service->draw(p, context); p.translate(0, heights); - } else { - paintDate(p); } context.outbg = _text1->hasOutLayout(); @@ -448,27 +440,6 @@ void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) { p.translate(0, height2); } -void BackgroundPreviewBox::paintDate(Painter &p) { - const auto date = _text1->Get(); - if (!date || !_serviceBg) { - return; - } - auto hq = PainterHighQualityEnabler(p); - const auto text = date->text; - const auto bubbleHeight = st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom(); - const auto bubbleTop = st::msgServiceMargin.top(); - const auto textWidth = st::msgServiceFont->width(text); - const auto bubbleWidth = st::msgServicePadding.left() + textWidth + st::msgServicePadding.right(); - const auto bubbleLeft = (width() - bubbleWidth) / 2; - const auto radius = bubbleHeight / 2; - p.setPen(Qt::NoPen); - p.setBrush(*_serviceBg); - p.drawRoundedRect(bubbleLeft, bubbleTop, bubbleWidth, bubbleHeight, radius, radius); - p.setPen(st::msgServiceFg); - p.setFont(st::msgServiceFont); - p.drawText(bubbleLeft + st::msgServicePadding.left(), bubbleTop + st::msgServicePadding.top() + st::msgServiceFont->ascent, text); -} - void BackgroundPreviewBox::radialAnimationCallback(crl::time now) { Expects(_paper.document() != nullptr); @@ -556,9 +527,29 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector &bg) { green += color.green(); blue += color.blue(); } - _serviceBg = Ui::ThemeAdjustedColor( - st::msgServiceBg->c, - QColor(red / count, green / count, blue / count)); + rpl::single( + rpl::empty + ) | rpl::then( + style::PaletteChanged() + ) | rpl::start_with_next([=] { + _serviceBg = Ui::ThemeAdjustedColor( + st::msgServiceBg->c, + QColor(red / count, green / count, blue / count)); + _chatStyle->applyAdjustedServiceBg(*_serviceBg); + }, lifetime()); + + _service = GenerateServiceItem( + delegate(), + _serviceHistory, + ((_forPeer && !_fromMessageId) + ? tr::lng_background_other_info( + tr::now, + lt_user, + _forPeer->shortName()) + : ItemDateText(_text1->data(), false)), + false); + _service->initDimensions(); + _service->resizeGetHeight(st::boxWideWidth); } void BackgroundPreviewBox::checkLoadedDocument() { diff --git a/Telegram/SourceFiles/boxes/background_preview_box.h b/Telegram/SourceFiles/boxes/background_preview_box.h index f758a77af..708361d7a 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.h +++ b/Telegram/SourceFiles/boxes/background_preview_box.h @@ -73,7 +73,6 @@ private: void paintImage(Painter &p); void paintRadial(Painter &p); void paintTexts(Painter &p, crl::time ms); - void paintDate(Painter &p); void createBlurCheckbox(); int textsTop() const; void startFadeInFrom(QPixmap previous); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 422818169..0b155f476 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -1558,7 +1558,9 @@ bool HistoryWidget::updateStickersByEmoji() { return (emoji != nullptr); } -void HistoryWidget::toggleChooseChatTheme(not_null peer) { +void HistoryWidget::toggleChooseChatTheme( + not_null peer, + std::optional show) { const auto update = [=] { updateInlineBotQuery(); updateControlsGeometry(); @@ -1567,7 +1569,7 @@ void HistoryWidget::toggleChooseChatTheme(not_null peer) { if (peer.get() != _peer) { return; } else if (_chooseTheme) { - if (isChoosingTheme()) { + if (isChoosingTheme() && !show.value_or(false)) { const auto was = base::take(_chooseTheme); if (Ui::InFocusChain(this)) { setInnerFocus(); @@ -1575,6 +1577,8 @@ void HistoryWidget::toggleChooseChatTheme(not_null peer) { update(); } return; + } else if (!show.value_or(true)) { + return; } else if (_voiceRecordBar->isActive()) { controller()->showToast({ tr::lng_chat_theme_cant_voice(tr::now) }); return; diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 778422cd8..12ff41f8e 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -226,7 +226,9 @@ public: void clearDelayedShowAtRequest(); void clearDelayedShowAt(); - void toggleChooseChatTheme(not_null peer); + void toggleChooseChatTheme( + not_null peer, + std::optional show = std::nullopt); [[nodiscard]] Ui::ChatTheme *customChatTheme() const; void applyCloudDraft(History *history); 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 0a5d63cf2..fd59d20ae 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp @@ -341,6 +341,9 @@ void ThemeDocument::prepareThumbnailFrom( _patternOpacity); original.setDevicePixelRatio(ratio); } + if (_serviceWidth) { + original = Images::Circle(std::move(original)); + } _thumbnail = Ui::PixmapFromImage(std::move(original)); _thumbnailGood = good; } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 0ca07a17e..e43864568 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1231,8 +1231,10 @@ void MainWidget::clearChooseReportMessages() { _history->setChooseReportMessagesDetails({}, nullptr); } -void MainWidget::toggleChooseChatTheme(not_null peer) { - _history->toggleChooseChatTheme(peer); +void MainWidget::toggleChooseChatTheme( + not_null peer, + std::optional show) { + _history->toggleChooseChatTheme(peer, show); } bool MainWidget::showHistoryInDifferentWindow( diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 5fc904207..fa33b5d83 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -215,7 +215,9 @@ public: Fn done); void clearChooseReportMessages(); - void toggleChooseChatTheme(not_null peer); + void toggleChooseChatTheme( + not_null peer, + std::optional show); void showHistory( PeerId peer, diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index 9b4eec4de..38afef763 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -439,6 +439,12 @@ void ChatStyle::apply(not_null theme) { } } +void ChatStyle::applyAdjustedServiceBg(QColor serviceBg) { + auto r = 0, g = 0, b = 0, a = 0; + serviceBg.getRgb(&r, &g, &b, &a); + msgServiceBg().set(uchar(r), uchar(g), uchar(b), uchar(a)); +} + void ChatStyle::assignPalette(not_null palette) { *static_cast(this) = *palette; style::internal::resetIcons(); diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 234928f07..7e459847d 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -165,6 +165,7 @@ public: explicit ChatStyle(not_null isolated); void apply(not_null theme); + void applyAdjustedServiceBg(QColor serviceBg); [[nodiscard]] rpl::producer<> paletteChanged() const { return _paletteChanged.events(); diff --git a/Telegram/SourceFiles/ui/chat/choose_theme_controller.cpp b/Telegram/SourceFiles/ui/chat/choose_theme_controller.cpp index 9f64c4963..7626038d6 100644 --- a/Telegram/SourceFiles/ui/chat/choose_theme_controller.cpp +++ b/Telegram/SourceFiles/ui/chat/choose_theme_controller.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/chat/choose_theme_controller.h" +#include "boxes/background_box.h" #include "ui/rp_widget.h" #include "ui/widgets/shadow.h" #include "ui/widgets/labels.h" @@ -35,7 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { namespace { -constexpr auto kDisableElement = "disable"_cs; +const auto kDisableElement = [] { return u"disable"_q; }; [[nodiscard]] QImage GeneratePreview(not_null theme) { const auto &background = theme->background(); @@ -164,6 +165,7 @@ ChooseThemeController::ChooseThemeController( , _topShadow(std::make_unique(parent)) , _content(_wrap->add(object_ptr(_wrap.get()))) , _inner(CreateChild(_content.get())) +, _disabledEmoji(Ui::Emoji::Find(QString::fromUtf8("\xe2\x9d\x8c"))) , _dark(Window::Theme::IsThemeDarkValue()) { init(parent->sizeValue()); } @@ -239,29 +241,45 @@ void ChooseThemeController::initButtons() { controls, tr::lng_chat_theme_apply(), st::defaultActiveButton); + const auto choose = CreateChild( + controls, + rpl::single(u"Change Wallpaper"_q), + st::defaultActiveButton); const auto skip = st::normalFont->spacew * 2; controls->resize( - skip + cancel->width() + skip + apply->width() + skip, + skip + cancel->width() + skip + choose->width() + skip, apply->height() + skip * 2); rpl::combine( controls->widthValue(), cancel->widthValue(), - apply->widthValue() + apply->widthValue(), + choose->widthValue(), + _chosen.value() ) | rpl::start_with_next([=]( int outer, int cancelWidth, - int applyWidth) { - const auto inner = skip + cancelWidth + skip + applyWidth + skip; + int applyWidth, + int chooseWidth, + QString chosen) { + const auto was = _peer->themeEmoji(); + const auto now = (chosen == kDisableElement()) ? QString() : chosen; + const auto changed = (now != was); + apply->setVisible(changed); + choose->setVisible(!changed); + const auto shown = changed ? apply : choose; + const auto shownWidth = changed ? applyWidth : chooseWidth; + const auto inner = skip + cancelWidth + skip + shownWidth + skip; const auto left = (outer - inner) / 2; cancel->moveToLeft(left, 0); - apply->moveToRight(left, 0); + shown->moveToRight(left, 0); }, controls->lifetime()); cancel->setClickedCallback([=] { close(); }); apply->setClickedCallback([=] { if (const auto chosen = findChosen()) { - if (Ui::Emoji::Find(_peer->themeEmoji()) != chosen->emoji) { - const auto now = chosen->key ? _chosen : QString(); + const auto was = _peer->themeEmoji(); + const auto now = chosen->key ? _chosen.current() : QString(); + if (was != now) { _peer->setThemeEmoji(now); if (chosen->theme) { // Remember while changes propagate through event loop. @@ -278,6 +296,9 @@ void ChooseThemeController::initButtons() { } _controller->toggleChooseChatTheme(_peer); }); + choose->setClickedCallback([=] { + _controller->show(Box(_controller, _peer)); + }); } void ChooseThemeController::paintEntry(QPainter &p, const Entry &entry) { @@ -338,7 +359,7 @@ void ChooseThemeController::initList() { } else if (entry->key) { return entry->emoji->text(); } else { - return kDisableElement.utf16(); + return kDisableElement(); } }; _inner->events( @@ -375,7 +396,7 @@ void ChooseThemeController::initList() { const auto mouse = static_cast(event.get()); const auto entry = byPoint(mouse->pos()); const auto chosen = chosenText(entry); - if (entry && chosen == _pressed && chosen != _chosen) { + if (entry && chosen == _pressed && chosen != _chosen.current()) { clearCurrentBackgroundState(); if (const auto was = findChosen()) { was->chosen = false; @@ -465,13 +486,14 @@ void ChooseThemeController::clearCurrentBackgroundState() { } auto ChooseThemeController::findChosen() -> Entry* { - if (_chosen.isEmpty()) { + const auto chosen = _chosen.current(); + if (chosen.isEmpty()) { return nullptr; } for (auto &entry : _entries) { - if (!entry.key && _chosen == kDisableElement.utf16()) { + if (!entry.key && chosen == kDisableElement()) { return &entry; - } else if (_chosen == entry.emoji->text()) { + } else if (chosen == entry.emoji->text()) { return &entry; } } @@ -494,11 +516,14 @@ void ChooseThemeController::fill( _inner->resize(full, skip + single.height() + skip); const auto initial = Ui::Emoji::Find(_peer->themeEmoji()); + if (!initial) { + _chosen = kDisableElement(); + } _dark.value( ) | rpl::start_with_next([=](bool dark) { clearCurrentBackgroundState(); - if (_chosen.isEmpty() && initial) { + if (_chosen.current().isEmpty() && initial) { _chosen = initial->text(); } @@ -507,9 +532,9 @@ void ChooseThemeController::fill( auto x = skip * 2; _entries.push_back({ .preview = GenerateEmptyPreview(), - .emoji = Ui::Emoji::Find(QString::fromUtf8("\xe2\x9d\x8c")), + .emoji = _disabledEmoji, .geometry = QRect(QPoint(x, skip), single), - .chosen = (_chosen == kDisableElement.utf16()), + .chosen = (_chosen.current() == kDisableElement()), }); Assert(_entries.front().emoji != nullptr); style::PaletteChanged( @@ -528,7 +553,7 @@ void ChooseThemeController::fill( continue; } const auto key = ChatThemeKey{ theme.id, dark }; - const auto isChosen = (_chosen == emoji->text()); + const auto isChosen = (_chosen.current() == emoji->text()); _entries.push_back({ .key = key, .emoji = emoji, @@ -552,7 +577,7 @@ void ChooseThemeController::fill( const auto theme = data.get(); i->theme = std::move(data); i->preview = GeneratePreview(theme); - if (_chosen == i->emoji->text()) { + if (_chosen.current() == i->emoji->text()) { _controller->overridePeerTheme( _peer, i->theme, diff --git a/Telegram/SourceFiles/ui/chat/choose_theme_controller.h b/Telegram/SourceFiles/ui/chat/choose_theme_controller.h index ce97efe36..603c57839 100644 --- a/Telegram/SourceFiles/ui/chat/choose_theme_controller.h +++ b/Telegram/SourceFiles/ui/chat/choose_theme_controller.h @@ -66,9 +66,10 @@ private: const not_null _content; const not_null _inner; + const EmojiPtr _disabledEmoji = nullptr; std::vector _entries; QString _pressed; - QString _chosen; + rpl::variable _chosen; std::optional _pressPosition; std::optional _dragStartPosition; int _dragStartInnerLeft = 0; diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index 570dd7aaf..1ad731fc4 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -95,6 +95,33 @@ struct ResolvedPaper { }) | rpl::flatten_latest(); } +[[nodiscard]] rpl::producer<> DebouncedPaletteValue() { + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + + struct State { + base::has_weak_ptr guard; + bool scheduled = false; + }; + const auto state = lifetime.make_state(); + + consumer.put_next_copy(rpl::empty); + style::PaletteChanged( + ) | rpl::start_with_next([=] { + if (state->scheduled) { + return; + } + state->scheduled = true; + Ui::PostponeCall(&state->guard, [=] { + state->scheduled = false; + consumer.put_next_copy(rpl::empty); + }); + }, lifetime); + + return lifetime; + }; +} + struct ResolvedTheme { std::optional theme; std::optional paper; @@ -111,9 +138,22 @@ struct ResolvedTheme { ) | rpl::map([]( std::optional theme, std::optional paper, - bool night) { - return ResolvedTheme{ std::move(theme), std::move(paper), night }; - }); + bool night) -> rpl::producer { + if (theme || !paper) { + return rpl::single({ + std::move(theme), + std::move(paper), + night, + }); + } + return DebouncedPaletteValue( + ) | rpl::map([=] { + return ResolvedTheme{ + .paper = paper, + .dark = night, + }; + }); + }) | rpl::flatten_latest(); } } // namespace diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index a5645879a..df7a665c1 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -97,9 +97,10 @@ 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 PrepareCurrentCallback() { + const auto copy = std::make_shared(); + return [=, data = style::main_palette::save()](style::palette &palette) { + palette.load(data); }; } @@ -788,6 +789,14 @@ SessionController::SessionController( pushDefaultChatBackground(); } }, _lifetime); + style::PaletteChanged( + ) | rpl::start_with_next([=] { + for (auto &[key, value] : _customChatThemes) { + if (!key.theme.id) { + value.theme.reset(); + } + } + }, _lifetime); _authedName = session->user()->name(); session->changes().peerUpdates( @@ -1831,8 +1840,22 @@ void SessionController::showInNewWindow( } } -void SessionController::toggleChooseChatTheme(not_null peer) { - content()->toggleChooseChatTheme(peer); +void SessionController::toggleChooseChatTheme( + not_null peer, + std::optional show) { + content()->toggleChooseChatTheme(peer, show); +} + +void SessionController::finishChatThemeEdit(not_null peer) { + toggleChooseChatTheme(peer, false); + const auto weak = base::make_weak(this); + const auto history = activeChatCurrent().history(); + if (!history || history->peer != peer) { + showPeerHistory(peer); + } + if (weak) { + hideLayer(); + } } void SessionController::updateColumnLayout() { @@ -2082,7 +2105,7 @@ auto SessionController::cachedChatThemeValue( return rpl::single(_defaultChatTheme); } const auto settings = data.settings.find(type); - if (!data.id && settings == end(data.settings)) { + if (data.id && settings == end(data.settings)) { return rpl::single(_defaultChatTheme); } if (paper.isNull() @@ -2242,7 +2265,7 @@ void SessionController::cacheChatTheme( .key = key.theme, .preparePalette = (data.id ? PreparePaletteCallback(dark, i->second.accentColor) - : PrepareDefaultPaletteCallback()), + : PrepareCurrentCallback()), .backgroundData = backgroundData(theme), .bubblesData = PrepareBubblesData(data, type), .basedOnDark = dark, diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 01b9a8761..b06dd32c7 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -498,7 +498,10 @@ public: not_null peer, MsgId msgId = ShowAtUnreadMsgId); - void toggleChooseChatTheme(not_null peer); + void toggleChooseChatTheme( + not_null peer, + std::optional show = std::nullopt); + void finishChatThemeEdit(not_null peer); [[nodiscard]] bool dialogsListFocused() const { return _dialogsListFocused.current();