Added stories support to profile top bar.

This commit is contained in:
23rd
2025-10-14 11:01:35 +03:00
parent 5c784081c2
commit 9ab4a8ab6e
2 changed files with 124 additions and 2 deletions
@@ -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<TopicIconView>(
_topic,
@@ -503,11 +508,13 @@ void TopBar::setupUserpicButton(not_null<Controller*> 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<QColor> 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<QColor> 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
@@ -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<QColor> &edgeColor);
void setupStoryOutline();
void updateStoryOutline(std::optional<QColor> edgeColor);
void paintStoryOutline(QPainter &p);
const not_null<PeerData*> _peer;
Data::ForumTopic *_topic = nullptr;
@@ -198,6 +202,10 @@ private:
std::unique_ptr<Ui::Animations::Simple> _giftsAppearing;
rpl::lifetime _giftsLoadingLifetime;
QBrush _storyOutlineBrush;
std::vector<Ui::OutlineSegment> _storySegments;
bool _hasStories = false;
};
} // namespace Info::Profile