diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index 745e5eef8..5137b5c99 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -163,7 +163,7 @@ StoriesRow::StoriesRow( void StoriesRow::applySegments(const Dialogs::Stories::Element &element) { Expects(element.unreadCount <= element.count); - _count = std::max(element.count, 1); + _count = int(std::max(element.count, 1U)); _unreadCount = element.unreadCount; refreshSegments(); } diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index 5b265f8a7..6c6991bd5 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -75,9 +75,9 @@ StoriesSourceInfo StoriesSource::info() const { return { .id = user->id, .last = ids.empty() ? 0 : ids.back().date, - .count = std::min(int(ids.size()), kMaxSegmentsCount), - .unreadCount = std::min(unreadCount(), kMaxSegmentsCount), - .premium = user->isPremium() ? 1 : 0, + .count = uint32(std::min(int(ids.size()), kMaxSegmentsCount)), + .unreadCount = uint32(std::min(unreadCount(), kMaxSegmentsCount)), + .premium = user->isPremium() ? 1U : 0U, }; } @@ -913,9 +913,25 @@ void Stories::markAsRead(FullStoryId id, bool viewed) { bool Stories::bumpReadTill(PeerId peerId, StoryId maxReadTill) { auto &till = _readTill[peerId]; + auto refreshItems = std::vector(); + const auto guard = gsl::finally([&] { + for (const auto id : refreshItems) { + _owner->refreshStoryItemViews({ peerId, id }); + } + }); if (till < maxReadTill) { + const auto from = till; till = maxReadTill; updateUserStoriesState(_owner->peer(peerId)); + const auto i = _stories.find(peerId); + if (i != end(_stories)) { + refreshItems = ranges::make_subrange( + i->second.lower_bound(from + 1), + i->second.lower_bound(till + 1) + ) | ranges::views::transform([](const auto &pair) { + return pair.first; + }) | ranges::to_vector; + } } const auto i = _all.find(peerId); if (i == end(_all) || i->second.readTill >= maxReadTill) { @@ -1443,21 +1459,40 @@ std::optional Stories::peerSourceState( (i != end(_readTill)) ? i->second : 0), }; } - if (!_readTillsRequestId) { - const auto api = &_owner->session().api(); - _readTillsRequestId = api->request(MTPstories_GetAllReadUserStories( - )).done([=](const MTPUpdates &result) { - _readTillReceived = true; - api->applyUpdates(result); - for (auto &[peer, maxId] : base::take(_pendingUserStateMaxId)) { - updateUserStoriesState(peer); - } - }).send(); - } + requestReadTills(); _pendingUserStateMaxId[peer] = storyMaxId; return std::nullopt; } +void Stories::requestReadTills() { + if (_readTillReceived || _readTillsRequestId) { + return; + } + const auto api = &_owner->session().api(); + _readTillsRequestId = api->request(MTPstories_GetAllReadUserStories( + )).done([=](const MTPUpdates &result) { + _readTillReceived = true; + api->applyUpdates(result); + for (auto &[peer, maxId] : base::take(_pendingUserStateMaxId)) { + updateUserStoriesState(peer); + } + for (const auto &storyId : base::take(_pendingReadTillItems)) { + _owner->refreshStoryItemViews(storyId); + } + }).send(); +} + +bool Stories::isUnread(not_null story) { + const auto till = _readTill.find(story->peer()->id); + if (till == end(_readTill) && !_readTillReceived) { + requestReadTills(); + _pendingReadTillItems.emplace(story->fullId()); + return false; + } + const auto readTill = (till != end(_readTill)) ? till->second : 0; + return (story->id() > readTill); +} + void Stories::updateUserStoriesState(not_null peer) { const auto till = _readTill.find(peer->id); const auto readTill = (till != end(_readTill)) ? till->second : 0; diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index c15b1fc6b..e1bb0dcb6 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -41,9 +41,9 @@ struct StoriesIds { struct StoriesSourceInfo { PeerId id = 0; TimeId last = 0; - int count : 15 = 0; - int unreadCount : 15 = 0; - int premium : 1 = 0; + uint32 count : 15 = 0; + uint32 unreadCount : 15 = 0; + uint32 premium : 1 = 0; friend inline bool operator==( StoriesSourceInfo, @@ -207,6 +207,7 @@ public: [[nodiscard]] std::optional peerSourceState( not_null peer, StoryId storyMaxId); + [[nodiscard]] bool isUnread(not_null story); private: struct Saved { @@ -241,6 +242,7 @@ private: void savedStateUpdated(not_null story); void sort(StorySourcesList list); bool bumpReadTill(PeerId peerId, StoryId maxReadTill); + void requestReadTills(); [[nodiscard]] std::shared_ptr lookupItem( not_null story); @@ -329,6 +331,7 @@ private: int _preloadingMainSourcesCounter = 0; base::flat_map _readTill; + base::flat_set _pendingReadTillItems; base::flat_map, StoryId> _pendingUserStateMaxId; mtpRequestId _readTillsRequestId = 0; bool _readTillReceived = false; diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp index 77f9aca72..326ec6db4 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp @@ -367,6 +367,9 @@ void Row::PaintCornerBadgeFrame( const auto st = context.st; const auto storiesUnreadBrush = [&] { + if (context.active) { + return st::dialogsUnreadBgActive->b; + } const auto left = st->padding.left(); const auto top = st->padding.top(); auto gradient = QLinearGradient( diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.h b/Telegram/SourceFiles/dialogs/dialogs_row.h index 735b03004..78e638093 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.h +++ b/Telegram/SourceFiles/dialogs/dialogs_row.h @@ -170,10 +170,10 @@ private: QImage frame; QImage cacheTTL; int frameIndex = -1; - int paletteVersion : 24 = 0; - int storiesShown : 1 = 0; - int storiesUnread : 1 = 0; - int active : 1 = 0; + uint32 paletteVersion : 24 = 0; + uint32 storiesShown : 1 = 0; + uint32 storiesUnread : 1 = 0; + uint32 active : 1 = 0; }; void setCornerBadgeShown( @@ -192,9 +192,9 @@ private: mutable std::unique_ptr _cornerBadgeUserpic; int _top = 0; int _height = 0; - int _index : 30 = 0; - int _cornerBadgeShown : 1 = 0; - int _topicJumpRipple : 1 = 0; + uint32 _index : 30 = 0; + uint32 _cornerBadgeShown : 1 = 0; + uint32 _topicJumpRipple : 1 = 0; }; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp index 329f61296..b882842ca 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp @@ -353,7 +353,7 @@ Content State::next() { .thumbnail = std::move(userpic), .count = info.count, .unreadCount = info.unreadCount, - .skipSmall = user->isSelf() ? 1 : 0, + .skipSmall = user->isSelf() ? 1U : 0U, }); } return result; @@ -424,8 +424,8 @@ rpl::producer LastForPeer(not_null peer) { result.elements.push_back({ .id = uint64(id), .thumbnail = MakeStoryThumbnail(*maybe), - .count = 1, - .unreadCount = (id > readTill) ? 1 : 0, + .count = 1U, + .unreadCount = (id > readTill) ? 1U : 0U, }); } } else if (maybe.error() == Data::NoStory::Unknown) { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h index 9bbca05b0..a3b44d04a 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h @@ -36,9 +36,9 @@ struct Element { uint64 id = 0; QString name; std::shared_ptr thumbnail; - int count : 15 = 0; - int unreadCount : 15 = 0; - int skipSmall : 1 = 0; + uint32 count : 15 = 0; + uint32 unreadCount : 15 = 0; + uint32 skipSmall : 1 = 0; friend inline bool operator==( const Element &a, diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 960a4cd16..a7f08a50d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -164,7 +164,7 @@ void Photo::unloadHeavyPart() { QSize Photo::countOptimalSize() { if (_serviceWidth > 0) { - return { _serviceWidth, _serviceWidth }; + return { int(_serviceWidth), int(_serviceWidth) }; } if (_parent->media() != this) { @@ -209,7 +209,7 @@ QSize Photo::countOptimalSize() { QSize Photo::countCurrentSize(int newWidth) { if (_serviceWidth) { - return { _serviceWidth, _serviceWidth }; + return { int(_serviceWidth), int(_serviceWidth) }; } const auto thumbMaxWidth = qMin(newWidth, st::maxMediaSize); const auto minWidth = std::clamp( diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index 31d2b08bc..f6d7062f0 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -167,10 +167,10 @@ private: const std::unique_ptr _spoiler; mutable QImage _imageCache; mutable std::optional _imageCacheRounding; - int _serviceWidth : 29 = 0; - mutable int _imageCacheForum : 1 = 0; - mutable int _imageCacheBlurred : 1 = 0; - mutable int _story : 1 = 0; + uint32 _serviceWidth : 29 = 0; + mutable uint32 _imageCacheForum : 1 = 0; + mutable uint32 _imageCacheBlurred : 1 = 0; + mutable uint32 _story : 1 = 0; }; 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 e425bdf65..57369d1fa 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp @@ -81,8 +81,7 @@ ServiceBox::ServiceBox( + _subtitle.countHeight(_maxWidth) + (_button.empty() ? 0 - : (st::msgServiceGiftBoxButtonMargins.top() - + _button.size.height())) + : (_content->buttonSkip() + _button.size.height())) + st::msgServiceGiftBoxButtonMargins.bottom())) , _innerSize(_size - QSize(0, st::msgServiceGiftBoxTopSkip)) { } @@ -165,6 +164,11 @@ TextState ServiceBox::textState(QPoint point, StateRequest request) const { if (rect.contains(point)) { result.link = _button.link; _button.lastPoint = point - rect.topLeft(); + } else if (contentRect().contains(point)) { + if (!_contentLink) { + _contentLink = _content->createViewLink(); + } + result.link = _contentLink; } } return result; 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 94347d401..1ae0c30ea 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_service_box.h +++ b/Telegram/SourceFiles/history/view/media/history_view_service_box.h @@ -23,6 +23,9 @@ public: [[nodiscard]] virtual QSize size() = 0; [[nodiscard]] virtual QString title() = 0; [[nodiscard]] virtual TextWithEntities subtitle() = 0; + [[nodiscard]] virtual int buttonSkip() { + return top(); + } [[nodiscard]] virtual QString button() = 0; virtual void draw( Painter &p, @@ -84,6 +87,7 @@ private: const not_null _parent; const std::unique_ptr _content; + mutable ClickHandlerPtr _contentLink; struct Button { void drawBg(QPainter &p) const; diff --git a/Telegram/SourceFiles/history/view/media/history_view_story_mention.cpp b/Telegram/SourceFiles/history/view/media/history_view_story_mention.cpp index fb9f62f29..a4d255fd2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_story_mention.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_story_mention.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "window/window_session_controller.h" #include "ui/boxes/confirm_box.h" +#include "ui/chat/chat_style.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/painter.h" @@ -39,13 +40,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { namespace { +constexpr auto kReadOutlineAlpha = 0.5; + } // namespace StoryMention::StoryMention( not_null parent, not_null story) : _parent(parent) -, _story(story) { +, _story(story) +, _unread(story->owner().stories().isUnread(story) ? 1 : 0) { } StoryMention::~StoryMention() = default; @@ -62,6 +66,10 @@ QString StoryMention::title() { return QString(); } +int StoryMention::buttonSkip() { + return st::storyMentionButtonSkip; +} + QString StoryMention::button() { return tr::lng_action_story_mention_button(tr::now); } @@ -86,7 +94,7 @@ void StoryMention::draw( Painter &p, const PaintContext &context, const QRect &geometry) { - const auto showStory = !_story->forbidsForward(); + const auto showStory = _story->forbidsForward() ? 0 : 1; if (!_thumbnail || _thumbnailFromStory != showStory) { using namespace Dialogs::Stories; const auto item = _parent->data(); @@ -97,18 +105,49 @@ void StoryMention::draw( ? history->session().user() : history->peer); _thumbnailFromStory = showStory; - _subscribed = false; + _subscribed = 0; } if (!_subscribed) { _thumbnail->subscribeToUpdates([=] { _parent->data()->history()->owner().requestViewRepaint(_parent); }); - _subscribed = true; + _subscribed = 1; } + const auto padding = (geometry.width() - st::storyMentionSize) / 2; + const auto size = geometry.width() - 2 * padding; p.drawImage( - geometry.topLeft(), - _thumbnail->image(st::msgServicePhotoWidth)); + geometry.topLeft() + QPoint(padding, padding), + _thumbnail->image(size)); + + const auto thumbnail = geometry.marginsRemoved( + QMargins(padding, padding, padding, padding)); + const auto added = 0.5 * (_unread + ? st::storyMentionUnreadSkipTwice + : st::storyMentionReadSkipTwice); + const auto outline = thumbnail.marginsAdded( + QMargins(added, added, added, added)); + if (_unread && _paletteVersion != style::PaletteVersion()) { + _paletteVersion = style::PaletteVersion(); + auto gradient = QLinearGradient( + outline.topRight(), + outline.bottomLeft()); + gradient.setStops({ + { 0., st::groupCallLive1->c }, + { 1., st::groupCallMuted1->c }, + }); + _unreadBrush = QBrush(gradient); + } + auto readColor = context.st->msgServiceFg()->c; + readColor.setAlphaF(std::min(readColor.alphaF(), kReadOutlineAlpha)); + p.setPen(QPen( + _unread ? _unreadBrush : QBrush(readColor), + 0.5 * (_unread + ? st::storyMentionUnreadStrokeTwice + : st::storyMentionReadStrokeTwice))); + p.setBrush(Qt::NoBrush); + auto hq = PainterHighQualityEnabler(p); + p.drawEllipse(outline); } void StoryMention::stickerClearLoopPlayed() { @@ -121,12 +160,12 @@ std::unique_ptr StoryMention::stickerTakePlayer( } bool StoryMention::hasHeavyPart() { - return _subscribed; + return _subscribed != 0; } void StoryMention::unloadHeavyPart() { if (_subscribed) { - _subscribed = false; + _subscribed = 0; _thumbnail->subscribeToUpdates(nullptr); } } diff --git a/Telegram/SourceFiles/history/view/media/history_view_story_mention.h b/Telegram/SourceFiles/history/view/media/history_view_story_mention.h index 9cdacebcb..fea24787f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_story_mention.h +++ b/Telegram/SourceFiles/history/view/media/history_view_story_mention.h @@ -32,6 +32,7 @@ public: QSize size() override; QString title() override; TextWithEntities subtitle() override; + int buttonSkip() override; QString button() override; void draw( Painter &p, @@ -57,8 +58,11 @@ private: const not_null _parent; const not_null _story; std::shared_ptr _thumbnail; - bool _thumbnailFromStory = false; - bool _subscribed = false; + QBrush _unreadBrush; + uint32 _paletteVersion : 29 = 0; + uint32 _thumbnailFromStory : 1 = 0; + uint32 _subscribed : 1 = 0; + uint32 _unread : 1 = 0; }; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 56f84a8e6..4119099de 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -841,7 +841,7 @@ searchInChatPeerList: PeerList(defaultPeerList) { } msgServiceGiftBoxSize: size(206px, 231px); // Plus msgServiceGiftBoxTopSkip. -msgServiceGiftBoxRadius: 12px; +msgServiceGiftBoxRadius: 20px; msgServiceGiftBoxTopSkip: 4px; msgServiceGiftBoxButtonHeight: 32px; msgServiceGiftBoxButtonPadding: margins(2px, 0px, 2px, 0px); @@ -915,3 +915,10 @@ backgroundSwitchToLight: IconButton(backgroundSwitchToDark) { icon: icon {{ "menu/header_mode_day", boxTitleCloseFg }}; iconOver: icon {{ "menu/header_mode_day", boxTitleCloseFgOver }}; } + +storyMentionSize: 80px; +storyMentionUnreadSkipTwice: 8px; +storyMentionUnreadStrokeTwice: 6px; +storyMentionReadSkipTwice: 7px; +storyMentionReadStrokeTwice: 3px; +storyMentionButtonSkip: 5px; diff --git a/Telegram/SourceFiles/ui/userpic_view.h b/Telegram/SourceFiles/ui/userpic_view.h index e05162f95..6e50127d7 100644 --- a/Telegram/SourceFiles/ui/userpic_view.h +++ b/Telegram/SourceFiles/ui/userpic_view.h @@ -25,8 +25,8 @@ struct PeerUserpicView { QImage cached; std::shared_ptr cloud; base::weak_ptr empty; - int paletteVersion : 31 = 0; - int forum : 1 = 0; + uint32 paletteVersion : 31 = 0; + uint32 forum : 1 = 0; }; [[nodiscard]] bool PeerUserpicLoading(const PeerUserpicView &view);