From 5d5cae7860a0d211c0c5ace81051ee441e5a7129 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 14 Sep 2023 12:29:14 +0400 Subject: [PATCH] Implement suggested reaction count. --- Telegram/SourceFiles/data/data_story.cpp | 57 ++++++++++++++++--- Telegram/SourceFiles/data/data_story.h | 2 + .../stories/media_stories_controller.cpp | 55 +++++++++++++----- .../media/stories/media_stories_controller.h | 1 + .../media/stories/media_stories_reactions.cpp | 51 ++++++++++++++++- .../media/stories/media_stories_reactions.h | 1 + .../stories/media_stories_recent_views.cpp | 2 +- .../SourceFiles/media/view/media_view.style | 8 ++- 8 files changed, 150 insertions(+), 27 deletions(-) diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index 03f5ef158..f51d7a390 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -411,16 +411,28 @@ Data::ReactionId Story::sentReactionId() const { void Story::setReactionId(Data::ReactionId id) { if (_sentReactionId != id) { const auto wasEmpty = _sentReactionId.empty(); + changeSuggestedReactionCount(_sentReactionId, -1); _sentReactionId = id; - auto flags = UpdateFlag::Reaction | UpdateFlag(); + changeSuggestedReactionCount(id, 1); + if (_views.known && _sentReactionId.empty() != wasEmpty) { const auto delta = wasEmpty ? 1 : -1; if (_views.reactions + delta >= 0) { _views.reactions += delta; - flags |= UpdateFlag::ViewsChanged; } } - session().changes().storyUpdated(this, flags); + session().changes().storyUpdated(this, UpdateFlag::Reaction); + } +} + +void Story::changeSuggestedReactionCount(Data::ReactionId id, int delta) { + if (id.empty() || !_peer->isChannel()) { + return; + } + for (auto &suggested : _suggestedReactions) { + if (suggested.reaction == id && suggested.count + delta >= 0) { + suggested.count += delta; + } } } @@ -539,11 +551,13 @@ void Story::applyFields( auto reactions = _views.reactions; auto viewers = std::vector>(); auto viewsKnown = _views.known; + auto reactionsCounts = base::flat_map(); if (const auto info = data.vviews()) { - views = info->data().vviews_count().v; - reactions = info->data().vreactions_count().value_or_empty(); + const auto &data = info->data(); + views = data.vviews_count().v; + reactions = data.vreactions_count().value_or_empty(); viewsKnown = true; - if (const auto list = info->data().vrecent_viewers()) { + if (const auto list = data.vrecent_viewers()) { viewers.reserve(list->v.size()); auto &owner = _peer->owner(); auto &&cut = list->v @@ -552,8 +566,33 @@ void Story::applyFields( viewers.push_back(owner.peer(peerFromUser(id))); } } + auto total = 0; + if (const auto list = data.vreactions()) { + reactionsCounts.reserve(list->v.size()); + for (const auto &reaction : list->v) { + const auto &data = reaction.data(); + const auto id = Data::ReactionFromMTP(data.vreaction()); + const auto count = data.vcount().v; + reactionsCounts[id] = count; + total += count; + } + } + if (!reaction.empty()) { + if (auto &mine = reactionsCounts[reaction]; !mine) { + mine = 1; + ++total; + } + } + if (reactions < total) { + reactions = total; + } } else { viewers = _recentViewers; + for (const auto &suggested : _suggestedReactions) { + if (const auto count = suggested.count) { + reactionsCounts[suggested.reaction] = count; + } + } } auto locations = std::vector(); auto suggestedReactions = std::vector(); @@ -563,7 +602,11 @@ void Story::applyFields( for (const auto &area : areas->v) { if (const auto location = ParseLocation(area)) { locations.push_back(*location); - } else if (const auto reaction = ParseSuggestedReaction(area)) { + } else if (auto reaction = ParseSuggestedReaction(area)) { + const auto i = reactionsCounts.find(reaction->reaction); + if (i != end(reactionsCounts)) { + reaction->count = i->second; + } suggestedReactions.push_back(*reaction); } } diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index 3c9ce0a4d..f5e0c93f9 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -100,6 +100,7 @@ struct StoryLocation { struct SuggestedReaction { StoryArea area; Data::ReactionId reaction; + int count = 0; bool flipped = false; bool dark = false; @@ -180,6 +181,7 @@ public: [[nodiscard]] TimeId lastUpdateTime() const; private: + void changeSuggestedReactionCount(Data::ReactionId id, int delta); void applyFields( StoryMedia media, const MTPDstoryItem &data, diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 09c39dee6..14bf29857 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -892,6 +892,9 @@ bool Controller::changeShown(Data::Story *story) { const auto id = story ? story->fullId() : FullStoryId(); const auto session = story ? &story->session() : nullptr; const auto sessionChanged = (_session != session); + + updateAreas(story); + if (_shown == id && !sessionChanged) { return false; } @@ -915,21 +918,6 @@ bool Controller::changeShown(Data::Story *story) { Data::Stories::Polling::Viewer); } - const auto &locations = story - ? story->locations() - : std::vector(); - const auto &suggestedReactions = story - ? story->suggestedReactions() - : std::vector(); - if (_locations != locations) { - _locations = locations; - _areas.clear(); - } - if (_suggestedReactions != suggestedReactions) { - _suggestedReactions = suggestedReactions; - _areas.clear(); - } - _viewed = false; invalidate_weak_ptrs(&_viewsLoadGuard); _reactions->hide(); @@ -963,6 +951,7 @@ void Controller::subscribeToSession() { _session->changes().storyUpdates( Data::StoryUpdate::Flag::Edited | Data::StoryUpdate::Flag::ViewsChanged + | Data::StoryUpdate::Flag::Reaction ) | rpl::filter([=](const Data::StoryUpdate &update) { return (update.story == this->story()); }) | rpl::start_with_next([=](const Data::StoryUpdate &update) { @@ -977,6 +966,7 @@ void Controller::subscribeToSession() { .self = update.story->peer()->isSelf(), .channel = update.story->peer()->isChannel(), }); + updateAreas(update.story); } }, _sessionLifetime); _sessionLifetime.add([=] { @@ -984,6 +974,41 @@ void Controller::subscribeToSession() { }); } +void Controller::updateAreas(Data::Story *story) { + const auto &locations = story + ? story->locations() + : std::vector(); + const auto &suggestedReactions = story + ? story->suggestedReactions() + : std::vector(); + if (_locations != locations) { + _locations = locations; + _areas.clear(); + } + const auto reactionsCount = int(suggestedReactions.size()); + if (_suggestedReactions.size() == reactionsCount && !_areas.empty()) { + for (auto i = 0; i != reactionsCount; ++i) { + const auto count = suggestedReactions[i].count; + if (_suggestedReactions[i].count != count) { + _suggestedReactions[i].count = count; + _areas[i + _locations.size()].reaction->updateCount(count); + } + if (_suggestedReactions[i] != suggestedReactions[i]) { + _suggestedReactions = suggestedReactions; + _areas.clear(); + break; + } + } + } else if (_suggestedReactions != suggestedReactions) { + _suggestedReactions = suggestedReactions; + _areas.clear(); + } + if (_areas.empty() || _suggestedReactions.empty()) { + return; + } + +} + PauseState Controller::pauseState() const { const auto inactive = !_windowActive || _replyActive diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 8110785b7..88ceea064 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -247,6 +247,7 @@ private: const std::vector &lists, int index); + void updateAreas(Data::Story *story); void reactionChosen(ReactionsMode mode, ChosenReaction chosen); const not_null _delegate; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index 865103348..1ba4014ce 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "main/main_session.h" #include "media/stories/media_stories_controller.h" +#include "lang/lang_tag.h" #include "ui/chat/chat_style.h" #include "ui/effects/emoji_fly_animation.h" #include "ui/effects/path_shift_gradient.h" @@ -55,6 +56,7 @@ constexpr auto kSuggestedTailSmallOffset = 0.697; constexpr auto kSuggestedTailBigRotation = -42.29; constexpr auto kSuggestedTailSmallRotation = -40.87; constexpr auto kSuggestedReactionSize = 0.7; +constexpr auto kSuggestedWithCountSize = 0.55; class ReactionView final : public Ui::RpWidget @@ -67,6 +69,7 @@ public: const Data::SuggestedReaction &reaction); void setAreaGeometry(QRect geometry) override; + void updateCount(int count) override; private: using Element = HistoryView::Element; @@ -85,6 +88,8 @@ private: std::unique_ptr _pathGradient; AdminLog::OwnedItem _fake; QImage _background; + QString _countShort; + Ui::Text::String _counter; QRectF _bubbleGeometry; int _size = 0; int _mediaLeft = 0; @@ -183,6 +188,9 @@ ReactionView::ReactionView( } }, lifetime()); + _data.count = 0; + updateCount(reaction.count); + setAttribute(Qt::WA_TransparentForMouseEvents); show(); } @@ -202,6 +210,24 @@ void ReactionView::setAreaGeometry(QRect geometry) { setGeometry(geometry.marginsAdded({ add, add, add, add })); } +void ReactionView::updateCount(int count) { + if (_data.count == count) { + return; + } + _data.count = count; + const auto countShort = Lang::FormatCountToShort(count).string; + if (_countShort == countShort) { + return; + } + _countShort = countShort; + if (!count) { + _counter = {}; + } else { + _counter = { st::storiesLikeCountStyle, _countShort }; + } + update(); +} + not_null ReactionView::delegate() { return static_cast(this); } @@ -215,15 +241,34 @@ void ReactionView::paintEvent(QPaintEvent *e) { } p.drawImage(0, 0, _background); + const auto counter = !_counter.isEmpty(); + const auto scale = counter + ? kSuggestedWithCountSize + : kSuggestedReactionSize; + const auto counterSkip = counter + ? ((kSuggestedReactionSize - kSuggestedWithCountSize) + * _mediaHeight / 2) + : 0; + auto hq = PainterHighQualityEnabler(p); p.translate(_bubbleGeometry.center()); p.scale( - kSuggestedReactionSize * _bubbleGeometry.width() / _mediaWidth, - kSuggestedReactionSize * _bubbleGeometry.height() / _mediaHeight); + scale * _bubbleGeometry.width() / _mediaWidth, + scale * _bubbleGeometry.height() / _mediaHeight); p.rotate(_data.area.rotation); p.translate( -(_mediaLeft + (_mediaWidth / 2)), - -(_mediaTop + (_mediaHeight / 2))); + -(_mediaTop + (_mediaHeight / 2) + counterSkip)); + + if (counter) { + p.setPen(_data.dark ? Qt::white : Qt::black); + _counter.draw( + p, + _mediaLeft, + _mediaTop + _mediaHeight, + _mediaWidth, + style::al_top); + } auto context = Ui::ChatPaintContext{ .st = _chatStyle.get(), diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.h b/Telegram/SourceFiles/media/stories/media_stories_reactions.h index baf6ec0e8..01c679e2e 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.h @@ -46,6 +46,7 @@ public: virtual ~SuggestedReactionView() = default; virtual void setAreaGeometry(QRect geometry) = 0; + virtual void updateCount(int count) = 0; }; class Reactions final { diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp index bf9129ed2..3098dea67 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp @@ -323,7 +323,7 @@ void RecentViews::setupViewsReactions() { ) | rpl::start_with_next([=](int width) { width += width ? st::storiesLikesTextRightSkip - : st::storiesLieksEmptyRightSkip; + : st::storiesLikesEmptyRightSkip; _likeWrap->resize(likes->x() + width, _likeIcon->height()); updateViewsReactionsGeometry(); }, _likeWrap->lifetime()); diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index 6bba6ecdb..bafcd0574 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -1007,4 +1007,10 @@ storiesLikesText: FlatLabel(defaultFlatLabel) { } storiesLikesTextPosition: point(41px, 14px); storiesLikesTextRightSkip: 8px; -storiesLieksEmptyRightSkip: 2px; +storiesLikesEmptyRightSkip: 2px; + +storiesLikeCountStyle: TextStyle(defaultTextStyle) { + font: font(32px semibold); + linkFont: font(32px semibold); + linkFontOver: font(32px semibold underline); +}