From 9ab4a8ab6ea2a52f8f28e48c37912e45da77609e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 14 Oct 2025 11:01:35 +0300 Subject: [PATCH] Added stories support to profile top bar. --- .../info/profile/info_profile_top_bar.cpp | 118 +++++++++++++++++- .../info/profile/info_profile_top_bar.h | 8 ++ 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/info/profile/info_profile_top_bar.cpp b/Telegram/SourceFiles/info/profile/info_profile_top_bar.cpp index 33d8db0432..0b895e0a46 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_top_bar.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_top_bar.cpp @@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo.h" #include "data/data_saved_sublist.h" #include "data/data_session.h" +#include "data/data_stories.h" #include "data/data_user.h" #include "data/notify/data_notify_settings.h" #include "data/notify/data_peer_notify_settings.h" @@ -59,6 +60,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rect.h" #include "ui/top_background_gradient.h" #include "ui/ui_utility.h" +#include "ui/effects/outline_segments.h" #include "ui/widgets/buttons.h" #include "ui/widgets/horizontal_fit_container.h" #include "ui/widgets/labels.h" @@ -84,6 +86,8 @@ constexpr auto kGiftBadgeGlares = 3; constexpr auto kMinPatternRadius = 8; constexpr auto kBgOpacity = 40. / 255.; constexpr auto kMinContrast = 5.5; +constexpr auto kStoryOutlineFadeEnd = 0.4; +constexpr auto kStoryOutlineFadeRange = 1. - kStoryOutlineFadeEnd; using AnimatedPatternPoint = TopBar::AnimatedPatternPoint; @@ -267,6 +271,7 @@ TopBar::TopBar( setupButtons(controller, descriptor.backToggles.value()); setupUserpicButton(controller); setupActions(controller); + setupStoryOutline(); if (_topic) { _topicIconView = std::make_unique( _topic, @@ -503,11 +508,13 @@ void TopBar::setupUserpicButton(not_null controller) { ) | rpl::start_with_next([=] { _userpicButton->setAttribute( Qt::WA_TransparentForMouseEvents, - !_peer->userpicPhotoId()); + !_peer->userpicPhotoId() && !_hasStories); updateVideoUserpic(); }, lifetime()); _userpicButton->setClickedCallback([=] { - if (const auto id = _peer->userpicPhotoId()) { + if (_hasStories) { + controller->parentController()->openPeerStories(_peer->id); + } else if (const auto id = _peer->userpicPhotoId()) { if (const auto photo = _peer->owner().photo(id); photo->date()) { controller->parentController()->openPhoto(photo, _peer); } @@ -869,6 +876,7 @@ void TopBar::paintEvent(QPaintEvent *e) { paintPinnedToTopGifts(p, rect()); } paintUserpic(p); + paintStoryOutline(p); } void TopBar::setupButtons( @@ -1439,4 +1447,110 @@ void TopBar::paintPinnedToTopGifts(QPainter &p, const QRect &rect) { p.setOpacity(1.); } +void TopBar::setupStoryOutline() { + const auto user = _peer->asUser(); + if (!user) { + return; + } + + rpl::combine( + _edgeColor.value(), + rpl::merge( + rpl::single(rpl::empty_value()), + style::PaletteChanged(), + user->session().changes().peerUpdates( + Data::PeerUpdate::Flag::StoriesState + ) | rpl::filter([=](const Data::PeerUpdate &update) { + return update.peer == user; + }) | rpl::to_empty) + ) | rpl::start_with_next([=]( + std::optional edgeColor, + rpl::empty_value) { + const auto geometry = userpicGeometry(); + _storyOutlineBrush = Ui::UnreadStoryOutlineGradient( + QRectF( + geometry.x(), + geometry.y(), + geometry.width(), + geometry.height())); + updateStoryOutline(edgeColor); + }, lifetime()); +} + +void TopBar::updateStoryOutline(std::optional edgeColor) { + const auto user = _peer->asUser(); + if (!user) { + return; + } + + const auto hasActiveStories = user->hasActiveStories(); + + if (_hasStories != hasActiveStories) { + _hasStories = hasActiveStories; + update(); + } + + if (!hasActiveStories) { + _storySegments.clear(); + return; + } + + _storySegments.clear(); + const auto &stories = user->owner().stories(); + const auto source = stories.source(user->id); + if (!source) { + return; + } + + const auto baseColor = edgeColor + ? Ui::BlendColors(*edgeColor, Qt::white, .5) + : _storyOutlineBrush.color(); + const auto unreadBrush = edgeColor + ? QBrush(baseColor) + : _storyOutlineBrush; + const auto readBrush = edgeColor + ? QBrush(anim::with_alpha(baseColor, 0.5)) + : QBrush(st::dialogsUnreadBgMuted->b); + + const auto readTill = source->readTill; + const auto widthBig = style::ConvertFloatScale(3.0); + const auto widthSmall = widthBig / 2.; + for (const auto &storyIdDates : source->ids) { + const auto isUnread = (storyIdDates.id > readTill); + _storySegments.push_back({ + .brush = isUnread ? unreadBrush : readBrush, + .width = !isUnread ? widthSmall : widthBig, + }); + } +} + +void TopBar::paintStoryOutline(QPainter &p) { + if (!_hasStories || _storySegments.empty()) { + return; + } + auto hq = PainterHighQualityEnabler(p); + + const auto progress = _progress.current(); + const auto alpha = std::clamp( + (progress - kStoryOutlineFadeEnd) / kStoryOutlineFadeRange, + 0., + 1.); + if (alpha <= 0.) { + return; + } + + p.setOpacity(alpha); + + const auto geometry = userpicGeometry(); + const auto outlineWidth = style::ConvertFloatScale(4.0); + const auto padding = style::ConvertFloatScale(3.0); + const auto outlineRect = QRectF(geometry).adjusted( + -padding - outlineWidth / 2, + -padding - outlineWidth / 2, + padding + outlineWidth / 2, + padding + outlineWidth / 2); + + Ui::PaintOutlineSegments(p, outlineRect, _storySegments); +} + } // namespace Info::Profile diff --git a/Telegram/SourceFiles/info/profile/info_profile_top_bar.h b/Telegram/SourceFiles/info/profile/info_profile_top_bar.h index d744727950..ad20d3a0ce 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_top_bar.h +++ b/Telegram/SourceFiles/info/profile/info_profile_top_bar.h @@ -29,6 +29,7 @@ class MultiPlayer; namespace Ui { class VideoUserpicPlayer; +struct OutlineSegment; namespace Text { class CustomEmoji; } // namespace Text @@ -132,6 +133,9 @@ private: void setupPinnedToTopGifts(); void paintPinnedToTopGifts(QPainter &p, const QRect &rect); void adjustColors(const std::optional &edgeColor); + void setupStoryOutline(); + void updateStoryOutline(std::optional edgeColor); + void paintStoryOutline(QPainter &p); const not_null _peer; Data::ForumTopic *_topic = nullptr; @@ -198,6 +202,10 @@ private: std::unique_ptr _giftsAppearing; rpl::lifetime _giftsLoadingLifetime; + QBrush _storyOutlineBrush; + std::vector _storySegments; + bool _hasStories = false; + }; } // namespace Info::Profile