From f3db7e636bdb5e934a391bac6a5ca1646bbf87b3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 13 Sep 2023 10:30:16 +0400 Subject: [PATCH] Implement channel stories views / reactions. --- Telegram/Resources/icons/mediaview/views.png | Bin 0 -> 476 bytes .../Resources/icons/mediaview/views@2x.png | Bin 0 -> 884 bytes .../Resources/icons/mediaview/views@3x.png | Bin 0 -> 1321 bytes Telegram/SourceFiles/data/data_changes.h | 14 +- Telegram/SourceFiles/data/data_story.cpp | 30 +++- Telegram/SourceFiles/data/data_story.h | 1 + Telegram/SourceFiles/lang/lang_tag.cpp | 6 +- Telegram/SourceFiles/lang/lang_tag.h | 7 +- .../stories/media_stories_controller.cpp | 38 +++-- .../media/stories/media_stories_reactions.cpp | 22 ++- .../media/stories/media_stories_reactions.h | 3 +- .../stories/media_stories_recent_views.cpp | 138 ++++++++++++++++-- .../stories/media_stories_recent_views.h | 22 ++- .../SourceFiles/media/view/media_view.style | 17 +++ .../SourceFiles/ui/boxes/edit_invite_link.cpp | 2 +- 15 files changed, 247 insertions(+), 53 deletions(-) create mode 100644 Telegram/Resources/icons/mediaview/views.png create mode 100644 Telegram/Resources/icons/mediaview/views@2x.png create mode 100644 Telegram/Resources/icons/mediaview/views@3x.png diff --git a/Telegram/Resources/icons/mediaview/views.png b/Telegram/Resources/icons/mediaview/views.png new file mode 100644 index 0000000000000000000000000000000000000000..e8496be337b3b5063dc0dd7db685e3eb29914365 GIT binary patch literal 476 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgf%+1rqF~maf z?PSB%O#u>ZkF)%ecolD4dXV(isjE*{i{qh^qjPk<*B@=(rbYjlIhm%4tzV>>FD} z+k%enr+c%%@P7I<@A~r}wa1Mm_PlOYQ}CSh*uqgj#5HhQs)*~l*Q-|j>KA+ddFT7z z@%&nS$sAuE^Bi8Wip%kn&HWs+%P*%qwz$3RvWe@zr$rleUQ2q59DV(@>(cYjGgBic zsg%aD|0=MU8xBlodw@7lS3%Rg4! zVPxWN;|+FM^Zam|aire#yCP3!EsMNvnq+JBZ&~KG*I$jfqJ&u(>oV><))#Dhn6&Z5 w@&_$Crwd#kY&}1pi80(IP(-GpTYSa=^VoXpcAa{+{h*NbboFyt=akR{09*pMXaE2J literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/mediaview/views@2x.png b/Telegram/Resources/icons/mediaview/views@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..30b516900c227441cb9220875e350f99ca77d6b2 GIT binary patch literal 884 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1SD@H--)t)WAjH+G zuCBf`=%sr1>ZrB9YV0O@98qLcUv>O(WmQ#D;7N0@%{RYP?VTg!tFA55<)ijlm;ZQs zjotp8J1uR6rQ7_LXK%lKgz4S;_vg=@D=RO*e(lXNiIa!#>%gbFpoj%=tFd<>` z#LnccQHd)baqio<&uQU>Q>VDz?b)-ZrmD(Hs<&YM!Gs4TR)yl*PMkZ(w{h0Tikgaw z6Ea8NmetqQJu)rb9Xnyp&FML@@$v2YP3|lzQoTy@H$F`CP?_tOzWHW^4%=$pndi=* z?|j$m=BzYPLF)GG?r!d+9nYVqZ<+kI?6>^s)t6tsdH+7x@Dc(_qs381&P4CWbFN_-h}`jpH$ecC(8(Y|BF%^bBy42QTD__!^e*w^gS(PR9u__2b*lcu)AiWB!b6qJ@u zmFSHL*chR*iSf>@TZ?=yIKH$v(Q?A#^Rs7c?3oky?%lh8?~25T1-||!Dngu(s$x!T z^H-aEWQmW$7WG}hDd(C6<2<#d8a-XM+3|Hnef{y$S<6emRM}=6E0J9Oc!TA_-w#aB zUW@tt*Uswr^5@HvGq&CeTitq{g=6_gX1@=tJ@O$_y_Oc8W8`(qnd;^G{haY5cX#)% zRdY2&nK|0OK5~!v!e;P9US2*vKHfN_!Kc9H*WWr5sRasW%#HuCt-P8gad1KTp?rZe z4jX#w|MO~1Ki%rIFl@Eq^Ji=KFu~yerA+%7zO^q241F@C5R~FQUHx3vIVCg!0BG`r Ap#T5? literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/mediaview/views@3x.png b/Telegram/Resources/icons/mediaview/views@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..523f11cc8842d20bc759136f97ac67a48bf0cd81 GIT binary patch literal 1321 zcmZ`(eK->c7~h91Yx&r--CcN^uRM$vX=>4xnYAf03$s+`YlwWbYI9YBq=45oPq0rsUKi6OPKF{y{{oeQe{r5ian-dgxbUV}%3IG7M zlSzbN1Cutf6=cZcKRB2h2*?aRiU%|gSkD;>LM)BU2nYb64SFj8n4JJH+O!y8X#fBK z76AcZ0|PgEBG7+Rk_h}+ZyJl-iSGaaqY^T~=Vbhrg>aQilMmwXC8=6YAqXQhIS}tQ z?@8cC$OgEB6C^aR&Zu?Zi1=)inc$rcX-*V7i8;RT*iX!XJ<|7B27~LdzP^$)8`)WZ z1Dn_sKEGdmH~gWt$Lv#r(LRtRu;423h`q?-#n$-Gr46ymma$Z&n7hbeFtmo4nW?L< z|7T+rXI-eirCwfM9vvOMr%Z{C)+{fNyeD6F0&;Wn@*ogMN=nKs{1NmWLVg3F66bj`66Rm}snl|6LBkkyzrk%^V$Yc%=4S{MA zR>_T>BGKgJWS8I*s9K>=2n6uqz7g2k`np6Sk;!D+V2|84mX-!pDl0hr_A{_>w4$P- zyW4NaP36GA+S(f0#l^yEiOtK-uCJ@RUS7Vc*H=|l^~Tfa^xT4ineeIebUa?J)rwy@ z>e|Q0=_N;IXJ^BxRIrlQxU#ZxHBa*CL*pJCE}TK_S1O}JWVwG`DlRUjQmJ?~27?Kv zQa_?~)S8+aHjDK=F~6{|(iM?tT-Gd;o&Ax;cmJH4+L=H=xzVPfYNRZY5LtzWDlAIw5K$37`IyUoO< zp;C1vBSSRAd;HiFQ}3f&Ty*S*LZSEzN%`%GRYGBuq$KML6$v!a?=#f%>{+kLg9i^% z2%XwE2%e1as)m=>pblbaEZs= z%)NooSb(a$Q@N|GtW02;wh+akJ=bXLclf38qB(7d+J@{4FK&`dYeLT3fN?Ve;e>>J5FD{Rh3CfWg8k zJDmTtdFU~VIPkc$vyeF)!AnFWIP`hqaAej*Yg}AhT8vdqDg3)Y?0pYglEtCLH)Au& zXLCusb)7CLeQIYyQBU%H(3o1KQk7aI{T2t&4-Q&fH@WZmOR)K=*vZuZ|B%p7$3zKC z%@r7#!C<|#d7xP{jmc!{P$Wv}nNvZI=0uKJ#9msO_IZpV7gNc92hBr~$>cHVOLWWW zd*#LhN+rc(zfI^6>G*MdsC#{TJ6&AyP%3rdbiPXes$o(t2T6lmx3E;MvZ)FD6sp%Q zv$Uh8#y9;bVn1cqVAgW>ZILkZHcA?_;+&!WAcRY%Y#Rd&2Y2Rj=@HPx*w`PfujOtp z>EPD+YuAili*; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index dc142bbd6..03f5ef158 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -410,8 +410,17 @@ Data::ReactionId Story::sentReactionId() const { void Story::setReactionId(Data::ReactionId id) { if (_sentReactionId != id) { + const auto wasEmpty = _sentReactionId.empty(); _sentReactionId = id; - session().changes().storyUpdated(this, UpdateFlag::Reaction); + auto flags = UpdateFlag::Reaction | UpdateFlag(); + 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); } } @@ -438,6 +447,7 @@ void Story::applyViewsSlice( || (_views.total != slice.total); _views.reactions = slice.reactions; _views.total = slice.total; + _views.known = true; if (offset.isEmpty()) { _views = slice; } else if (_views.nextOffset == offset) { @@ -468,14 +478,14 @@ void Story::applyViewsSlice( // Count not changed, but list of recent viewers changed. _peer->session().changes().storyUpdated( this, - UpdateFlag::ViewsAdded); + UpdateFlag::ViewsChanged); } } } if (changed) { _peer->session().changes().storyUpdated( this, - UpdateFlag::ViewsAdded); + UpdateFlag::ViewsChanged); } } @@ -528,9 +538,11 @@ void Story::applyFields( auto views = _views.total; auto reactions = _views.reactions; auto viewers = std::vector>(); + auto viewsKnown = _views.known; if (const auto info = data.vviews()) { views = info->data().vviews_count().v; reactions = info->data().vreactions_count().value_or_empty(); + viewsKnown = true; if (const auto list = info->data().vrecent_viewers()) { viewers.reserve(list->v.size()); auto &owner = _peer->owner(); @@ -577,8 +589,14 @@ void Story::applyFields( _edited = edited; _pinned = pinned; _noForwards = noForwards; - if (_views.reactions != reactions || _views.total != views) { - _views = StoryViews{ .reactions = reactions, .total = views }; + if (_views.reactions != reactions + || _views.total != views + || _views.known != viewsKnown) { + _views = StoryViews{ + .reactions = reactions, + .total = views, + .known = viewsKnown, + }; } if (viewsChanged) { _recentViewers = std::move(viewers); @@ -607,7 +625,7 @@ void Story::applyFields( if (!initial && (changed || viewsChanged || reactionChanged)) { _peer->session().changes().storyUpdated(this, UpdateFlag() | (changed ? UpdateFlag::Edited : UpdateFlag()) - | (viewsChanged ? UpdateFlag::ViewsAdded : UpdateFlag()) + | (viewsChanged ? UpdateFlag::ViewsChanged : UpdateFlag()) | (reactionChanged ? UpdateFlag::Reaction : UpdateFlag())); } if (!initial && (captionChanged || mediaChanged)) { diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index 342d5b03a..3c9ce0a4d 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -71,6 +71,7 @@ struct StoryViews { QString nextOffset; int reactions = 0; int total = 0; + bool known = false; }; struct StoryArea { diff --git a/Telegram/SourceFiles/lang/lang_tag.cpp b/Telegram/SourceFiles/lang/lang_tag.cpp index f11c00a0c..2b0f30544 100644 --- a/Telegram/SourceFiles/lang/lang_tag.cpp +++ b/Telegram/SourceFiles/lang/lang_tag.cpp @@ -937,6 +937,10 @@ ShortenedCount FormatCountToShort(int64 number) { return result; } +QString FormatCountDecimal(int64 number) { + return QString("%L1").arg(number); +} + PluralResult Plural( ushort keyBase, float64 value, @@ -973,7 +977,7 @@ PluralResult Plural( if (type == lt_count_short) { return { shift, shortened.string }; } else if (type == lt_count_decimal) { - return { shift, QString("%L1").arg(round) }; + return { shift, FormatCountDecimal(round) }; } return { shift, QString::number(round) }; } diff --git a/Telegram/SourceFiles/lang/lang_tag.h b/Telegram/SourceFiles/lang/lang_tag.h index 03368b73b..2efa0882c 100644 --- a/Telegram/SourceFiles/lang/lang_tag.h +++ b/Telegram/SourceFiles/lang/lang_tag.h @@ -14,13 +14,16 @@ namespace Lang { inline constexpr auto kTextCommandLangTag = 0x20; constexpr auto kTagReplacementSize = 4; -int FindTagReplacementPosition(const QString &original, ushort tag); +[[nodiscard]] int FindTagReplacementPosition( + const QString &original, + ushort tag); struct ShortenedCount { int64 number = 0; QString string; }; -ShortenedCount FormatCountToShort(int64 number); +[[nodiscard]] ShortenedCount FormatCountToShort(int64 number); +[[nodiscard]] QString FormatCountDecimal(int64 number); struct PluralResult { int keyShift = 0; diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index e0fc61089..09c39dee6 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -855,24 +855,32 @@ void Controller::show( if (!changeShown(story)) { return; } - _viewed = false; - invalidate_weak_ptrs(&_viewsLoadGuard); - _reactions->hide(); - if (_replyArea->focused()) { - unfocusReply(); - } _replyArea->show({ .peer = unsupported ? nullptr : peer.get(), .id = story->id(), }, _reactions->likedValue()); + const auto wasLikeButton = QPointer(_recentViews->likeButton()); _recentViews->show({ .list = story->recentViewers(), .reactions = story->reactions(), .total = story->views(), - .valid = peer->isSelf(), - }); + .self = peer->isSelf(), + .channel = peer->isChannel(), + }, _reactions->likedValue()); + if (const auto nowLikeButton = _recentViews->likeButton()) { + if (wasLikeButton != nowLikeButton) { + _reactions->attachToReactionButton(nowLikeButton); + } + } + + if (peer->isSelf() || peer->isChannel()) { + _reactions->setReactionIconWidget(_recentViews->likeIconWidget()); + } else if (const auto like = _replyArea->likeAnimationTarget()) { + _reactions->setReactionIconWidget(like); + } + _reactions->showLikeFrom(story); stories.loadAround(storyId, context); @@ -906,7 +914,6 @@ bool Controller::changeShown(Data::Story *story) { story, Data::Stories::Polling::Viewer); } - _reactions->showLikeFrom(story); const auto &locations = story ? story->locations() @@ -923,6 +930,14 @@ bool Controller::changeShown(Data::Story *story) { _areas.clear(); } + _viewed = false; + invalidate_weak_ptrs(&_viewsLoadGuard); + _reactions->hide(); + _reactions->setReactionIconWidget(nullptr); + if (_replyArea->focused()) { + unfocusReply(); + } + return true; } @@ -947,7 +962,7 @@ void Controller::subscribeToSession() { }, _sessionLifetime); _session->changes().storyUpdates( Data::StoryUpdate::Flag::Edited - | Data::StoryUpdate::Flag::ViewsAdded + | Data::StoryUpdate::Flag::ViewsChanged ) | rpl::filter([=](const Data::StoryUpdate &update) { return (update.story == this->story()); }) | rpl::start_with_next([=](const Data::StoryUpdate &update) { @@ -959,7 +974,8 @@ void Controller::subscribeToSession() { .list = update.story->recentViewers(), .reactions = update.story->reactions(), .total = update.story->views(), - .valid = update.story->peer()->isSelf(), + .self = update.story->peer()->isSelf(), + .channel = update.story->peer()->isChannel(), }); } }, _sessionLifetime); diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index 408c7b54f..865103348 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -445,7 +445,8 @@ void Reactions::Panel::collapse(Mode mode) { } } -void Reactions::Panel::attachToReactionButton(not_null button) { +void Reactions::Panel::attachToReactionButton( + not_null button) { base::install_event_filter(button, [=](not_null e) { if (e->type() == QEvent::ContextMenu && !button->isHidden()) { show(Reactions::Mode::Reaction); @@ -662,10 +663,17 @@ void Reactions::setReplyFieldState( } void Reactions::attachToReactionButton(not_null button) { - _likeButton = button; _panel->attachToReactionButton(button); } +void Reactions::setReactionIconWidget(Ui::RpWidget *widget) { + if (_likeIconWidget != widget) { + assignLikedId({}); + _likeIconWidget = widget; + _reactionAnimation = nullptr; + } +} + auto Reactions::attachToMenu( not_null menu, QPoint desiredPosition) @@ -751,7 +759,7 @@ void Reactions::ready() { void Reactions::animateAndProcess(Chosen &&chosen) { const auto like = (chosen.mode == Mode::Reaction); const auto wrap = _controller->wrap(); - const auto target = like ? _likeButton : wrap.get(); + const auto target = like ? _likeIconWidget : wrap.get(); const auto story = _controller->story(); if (!story || !target) { return; @@ -796,7 +804,7 @@ Fn Reactions::setLikedIdIconInit( return nullptr; } assignLikedId(id); - if (id.empty() || !_likeButton) { + if (id.empty() || !_likeIconWidget) { return nullptr; } return crl::guard(&_likeIconGuard, [=](Ui::ReactionFlyCenter center) { @@ -812,12 +820,12 @@ void Reactions::initLikeIcon( not_null owner, Data::ReactionId id, Ui::ReactionFlyCenter center) { - Expects(_likeButton != nullptr); + Expects(_likeIconWidget != nullptr); - _likeIcon = std::make_unique(_likeButton); + _likeIcon = std::make_unique(_likeIconWidget); const auto icon = _likeIcon.get(); icon->show(); - _likeButton->sizeValue() | rpl::start_with_next([=](QSize size) { + _likeIconWidget->sizeValue() | rpl::start_with_next([=](QSize size) { icon->setGeometry(QRect(QPoint(), size)); }, icon->lifetime()); diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.h b/Telegram/SourceFiles/media/stories/media_stories_reactions.h index 7f7d22aea..baf6ec0e8 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.h @@ -83,6 +83,7 @@ public: rpl::producer focused, rpl::producer hasSendText); void attachToReactionButton(not_null button); + void setReactionIconWidget(Ui::RpWidget *widget); using AttachStripResult = HistoryView::Reactions::AttachSelectorResult; [[nodiscard]] AttachStripResult attachToMenu( @@ -123,7 +124,7 @@ private: bool _replyFocused = false; bool _hasSendText = false; - Ui::RpWidget *_likeButton = nullptr; + Ui::RpWidget *_likeIconWidget = nullptr; rpl::variable _liked; base::has_weak_ptr _likeIconGuard; std::unique_ptr _likeIcon; diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp index 565cf5995..bf9129ed2 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/group_call_userpics.h" #include "ui/controls/who_reacted_context_action.h" #include "ui/layers/box_content.h" +#include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" #include "ui/painter.h" #include "ui/rp_widget.h" @@ -128,7 +129,24 @@ RecentViews::RecentViews(not_null controller) RecentViews::~RecentViews() = default; -void RecentViews::show(RecentViewsData data) { +void RecentViews::show( + RecentViewsData data, + rpl::producer likedValue) { + const auto guard = gsl::finally([&] { + if (_likeIcon && likedValue) { + std::move( + likedValue + ) | rpl::map([](const Data::ReactionId &id) { + return !id.empty(); + }) | rpl::start_with_next([=](bool liked) { + const auto icon = liked + ? &st::storiesComposeControls.liked + : &st::storiesLikesIcon; + _likeIcon->setIconOverride(icon, icon); + }, _likeIcon->lifetime()); + } + }); + if (_data == data) { return; } @@ -137,27 +155,49 @@ void RecentViews::show(RecentViewsData data) { || (_data.reactions != data.reactions); const auto usersChanged = !_userpics || (_data.list != data.list); _data = data; - if (!_data.valid) { + if (!_data.self) { _text = {}; _clickHandlerLifetime.destroy(); _userpicsLifetime.destroy(); _userpics = nullptr; _widget = nullptr; - return; + } else { + if (!_widget) { + setupWidget(); + } + if (!_userpics) { + setupUserpics(); + } + if (countersChanged) { + updateText(); + } + if (usersChanged) { + updateUserpics(); + } + refreshClickHandler(); } - if (!_widget) { - setupWidget(); + + if (!_data.channel) { + _likeIcon = nullptr; + _likeWrap = nullptr; + _viewsWrap = nullptr; + } else { + _viewsCounter = Lang::FormatCountDecimal(std::max(_data.total, 1)); + _likesCounter = _data.reactions + ? Lang::FormatCountDecimal(_data.reactions) + : QString(); + if (!_likeWrap || !_likeIcon || !_viewsWrap) { + setupViewsReactions(); + } } - if (!_userpics) { - setupUserpics(); - } - if (countersChanged) { - updateText(); - } - if (usersChanged) { - updateUserpics(); - } - refreshClickHandler(); +} + +Ui::RpWidget *RecentViews::likeButton() const { + return _likeWrap.get(); +} + +Ui::RpWidget *RecentViews::likeIconWidget() const { + return _likeIcon.get(); } void RecentViews::refreshClickHandler() { @@ -236,6 +276,74 @@ void RecentViews::setupWidget() { }, raw->lifetime()); } +void RecentViews::setupViewsReactions() { + _viewsWrap = std::make_unique(_controller->wrap()); + _likeWrap = std::make_unique(_controller->wrap()); + _likeIcon = std::make_unique( + _likeWrap.get(), + st::storiesComposeControls.like); + _likeIcon->setAttribute(Qt::WA_TransparentForMouseEvents); + + _controller->layoutValue( + ) | rpl::start_with_next([=](const Layout &layout) { + _outer = layout.views; + updateViewsReactionsGeometry(); + }, _likeWrap->lifetime()); + + const auto views = Ui::CreateChild( + _viewsWrap.get(), + _viewsCounter.value(), + st::storiesViewsText); + views->show(); + views->setAttribute(Qt::WA_TransparentForMouseEvents); + views->move(st::storiesViewsTextPosition); + + views->widthValue( + ) | rpl::start_with_next([=](int width) { + _viewsWrap->resize(views->x() + width, _likeIcon->height()); + updateViewsReactionsGeometry(); + }, _viewsWrap->lifetime()); + _viewsWrap->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(_viewsWrap.get()); + const auto &icon = st::storiesViewsIcon; + const auto top = (_viewsWrap->height() - icon.height()) / 2; + icon.paint(p, 0, top, _viewsWrap->width()); + }, _viewsWrap->lifetime()); + + _likeIcon->move(0, 0); + const auto likes = Ui::CreateChild( + _likeWrap.get(), + _likesCounter.value(), + st::storiesLikesText); + likes->show(); + likes->setAttribute(Qt::WA_TransparentForMouseEvents); + likes->move(st::storiesLikesTextPosition); + + likes->widthValue( + ) | rpl::start_with_next([=](int width) { + width += width + ? st::storiesLikesTextRightSkip + : st::storiesLieksEmptyRightSkip; + _likeWrap->resize(likes->x() + width, _likeIcon->height()); + updateViewsReactionsGeometry(); + }, _likeWrap->lifetime()); + + _viewsWrap->show(); + _likeIcon->show(); + _likeWrap->show(); + + _likeWrap->setClickedCallback([=] { + _controller->toggleLiked(); + }); +} + +void RecentViews::updateViewsReactionsGeometry() { + _viewsWrap->move(_outer.topLeft() + st::storiesViewsPosition); + _likeWrap->move(_outer.topLeft() + + QPoint(_outer.width() - _likeWrap->width(), 0) + + st::storiesLikesPosition); +} + void RecentViews::updatePartsGeometry() { const auto skip = st::storiesRecentViewsSkip; const auto full = _userpicsWidth + skip + _text.maxWidth(); diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.h b/Telegram/SourceFiles/media/stories/media_stories_recent_views.h index d469cbb5e..1e8f226fb 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.h +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.h @@ -13,9 +13,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { struct StoryView; +struct ReactionId; } // namespace Data namespace Ui { +class AbstractButton; +class IconButton; class RpWidget; class GroupCallUserpics; class PopupMenu; @@ -34,7 +37,8 @@ struct RecentViewsData { std::vector> list; int reactions = 0; int total = 0; - bool valid = false; + bool self = false; + bool channel = false; friend inline auto operator<=>( const RecentViewsData &, @@ -49,7 +53,12 @@ public: explicit RecentViews(not_null controller); ~RecentViews(); - void show(RecentViewsData data); + void show( + RecentViewsData data, + rpl::producer likedValue = nullptr); + + [[nodiscard]] Ui::RpWidget *likeButton() const; + [[nodiscard]] Ui::RpWidget *likeIconWidget() const; private: struct MenuEntry { @@ -69,6 +78,9 @@ private: void updatePartsGeometry(); void showMenu(); + void setupViewsReactions(); + void updateViewsReactionsGeometry(); + void addMenuRow(Data::StoryView entry, const QDateTime &now); void addMenuRowPlaceholder(not_null session); void rebuildMenuTail(); @@ -83,6 +95,12 @@ private: RecentViewsData _data; rpl::lifetime _userpicsLifetime; + rpl::variable _viewsCounter; + rpl::variable _likesCounter; + std::unique_ptr _viewsWrap; + std::unique_ptr _likeWrap; + std::unique_ptr _likeIcon; + base::unique_qptr _menu; rpl::lifetime _menuShortLifetime; std::vector _menuEntries; diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index 67e684764..6bba6ecdb 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -991,3 +991,20 @@ storiesStealthBoxBottom: 11px; storiesStealthToast: Toast(defaultMultilineToast) { maxWidth: 340px; } + +storiesViewsPosition: point(4px, 29px); +storiesViewsIcon: icon{{ "mediaview/views", storiesComposeGrayText }}; +storiesViewsText: FlatLabel(defaultFlatLabel) { + textFg: storiesComposeGrayText; +} +storiesViewsTextPosition: point(26px, 14px); + +storiesLikesPosition: point(0px, 29px); +storiesLikesIcon: icon {{ "chat/input_like", storiesComposeWhiteText }}; +storiesLikesText: FlatLabel(defaultFlatLabel) { + textFg: storiesComposeWhiteText; + style: semiboldTextStyle; +} +storiesLikesTextPosition: point(41px, 14px); +storiesLikesTextRightSkip: 8px; +storiesLieksEmptyRightSkip: 2px; diff --git a/Telegram/SourceFiles/ui/boxes/edit_invite_link.cpp b/Telegram/SourceFiles/ui/boxes/edit_invite_link.cpp index 388a258bd..b7805d503 100644 --- a/Telegram/SourceFiles/ui/boxes/edit_invite_link.cpp +++ b/Telegram/SourceFiles/ui/boxes/edit_invite_link.cpp @@ -213,7 +213,7 @@ void EditInviteLinkBox( ? tr::lng_group_invite_usage_any(tr::now) : !limit ? tr::lng_group_invite_usage_custom(tr::now) - : QString("%L1").arg(limit); + : Lang::FormatCountDecimal(limit); state->usageButtons.emplace( limit, addButton(usagesWrap, usageGroup, limit, text));