From 33207b78d5d8a0aee2fa1344234acddba9580004 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 2 Feb 2024 17:08:56 +0400 Subject: [PATCH] Show applied boosts in message bubbles. --- .../Resources/icons/stories/boosts_mini.png | Bin 0 -> 374 bytes .../icons/stories/boosts_mini@2x.png | Bin 0 -> 690 bytes .../icons/stories/boosts_mini@3x.png | Bin 0 -> 1030 bytes Telegram/SourceFiles/data/data_channel.cpp | 2 + Telegram/SourceFiles/data/data_channel.h | 1 + Telegram/SourceFiles/history/history_item.cpp | 47 ++++++----- Telegram/SourceFiles/history/history_item.h | 14 ++-- .../history/history_item_components.h | 5 ++ .../history/view/history_view_message.cpp | 75 +++++++++++++----- .../history/view/history_view_message.h | 3 +- Telegram/SourceFiles/ui/chat/chat.style | 5 ++ 11 files changed, 108 insertions(+), 44 deletions(-) create mode 100644 Telegram/Resources/icons/stories/boosts_mini.png create mode 100644 Telegram/Resources/icons/stories/boosts_mini@2x.png create mode 100644 Telegram/Resources/icons/stories/boosts_mini@3x.png diff --git a/Telegram/Resources/icons/stories/boosts_mini.png b/Telegram/Resources/icons/stories/boosts_mini.png new file mode 100644 index 0000000000000000000000000000000000000000..80c667ecc925aee7e1e52c961634c54ae3ff22b8 GIT binary patch literal 374 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K$e9(cMqhG>W; zCrBtQ*u8uAwr$f|TT`E%nb|UT&h+Wu|NsC0=+UFd&1rXc7PmLFH8eET|NqBmI%CF+ zKYxA}pPr`s!(_(n+5dljW?v!2v17*$E^hA0lO`2p1%!nB`S_UK#dHFT6i`R)0$XXb zoDEyHv}7kYZ7wM(@tb4u@!ehP4i0JQ+fA(8r%s=)tgJjg$C5d`?c9ys<@!g27W79) zMd`)wJ9GYg|2mGD|9^Z;UeG409kyi25}VpzAz@)+kz(A^FTPAx_kZ>JHS^uHgoFlH s)>#4FVdQ&MBb@024-wjsO4v literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/stories/boosts_mini@2x.png b/Telegram/Resources/icons/stories/boosts_mini@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..95a8063967eb63a0fe98456c6a3f58ede1d23540 GIT binary patch literal 690 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={W7>k44ofy`glX(f`xTHpSruq6Z zXaU(A42G$TlC0TWzSVF5FO4N|zKE$K4@1CxxWi(`lf z@7s{Qr*{TQ)O{9wT0E&SWkSD>8?za6YS1PL9=F9o|9IoOgTx|LQob5WXapL0&g5C5 zasSUctI|u4Yv(^d`TOmcAo$F>(`p|e1G2Zxh%fuw%F0M)GF3$ z?y`6Jy!-Fh@4vtOwyt8(>Z_Aarfk3cciQwT)-rvkpMI*aS$0`dU}eayyzLM9*qZ+< zEV%xfzn%HThE-R!B)D6d3cZ&K?QUqW?O74DQl|fS#>^DHc4vvW_0xB3zS$#@U+1cH zv1q5t*-7hfzYWt65#eNO&JUWr>0*Y<&JIPfZe20%f0>(aGHks4_C0rklH{B1w|g%= zeD1Vxf{W6^6WiNfM2jSt&Hnncre}qN!i(>}mtW?r*co#!#mH~@(@T< z|Kiy`YLa{R@3N4-^1R4WC~Iq!{iVHZ*Is|ser#N1H$PNM)USGL6r=lZsorN(+j$S> zB_z7_AAf9YnsX<8_E|L%E+7$8(wvS8a&Z%ClTq51BDb+S|J(><@ z4BZXq8_bhZy72nzg#4{hCt25Be{GRs$0K9;`>!0+45vx$vsuOcxA>?%Uhzcu*o-DV z-P2EtCf$EqCf?l|w)$!CIql_Pt0j5ZW=VCr_^1h&+8;}2|Ch%8k5MUk*}?>lT`r*X N;_2$=vd$@?2>?r_8nplb literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/stories/boosts_mini@3x.png b/Telegram/Resources/icons/stories/boosts_mini@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..423de820910c74308931d06baead868a87d93743 GIT binary patch literal 1030 zcmV+h1o``kP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFa7jc#R9Fe^SUF2;K@`^bTo5<3 zNTNyM)=5BI326g@pr){}u&}TYW2LRFAebgtDEZXuyzB%W7XU?5DGnkX3|MUvz70@f7S3rXbbar+wFE5vrlt^NC zbaV_24M|ANhc=t-{rx?aN(~MUiel~U?GFzRpP!$3d3hqQEjF9YPft(C2EV4JCKhXK zY`nd_1sMv3nA98;7Z=B3u|LVAzP`R#&uBDWUS1NvwY9~(w!mO81cO0R#ouHyNqVKF zrN_s|%+JowYMTc~KRi5SRe+0&3!0jrpBH(z+s!VS07E85a+~$__14x_k>`XY{OReb zXr>AFdc9%|2v1E-K`b{n7bi=cwOA~gB*8()$HzrI#$8=qu(4XL%mDDlVt186e}6wt zlPJj;Ck|pxr;{0gqobq0&EQw&4tBeJX=w=`MeYJ^bR+O5CnpYv1D&w0u5M&xgg2Jl zC0hoK?)CLmT)l{&pP%Id=nE|^Epq;QMq#;fhsw&ztE($C82J=2NY3SQU0+|*@c#Zj z5C{;isHotgzP!9Brk*aWGG9JRA=5@L#~dzyPl=bFjU=O=7*hz3hell*yL*VGk7*6)i381b!Ac@{dwWB#A_0%b!&B*csS2r3 z2=NWXsvjR8c(9Ve*w`5JJ3Bjtg@vkVD+WSmV?6@6-TM0a$QU~p`v!PS@_5c6?07*qoM6N<$f>9I0 AO8@`> literal 0 HcmV?d00001 diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index fecbe6287..e130dac18 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -1118,6 +1118,8 @@ void ApplyChannelUpdate( if (stickersChanged) { session->changes().peerUpdated(channel, UpdateFlag::StickersSet); } + + channel->mgInfo->boostsApplied = update.vboosts_applied().value_or_empty(); } channel->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty())); channel->setTranslationDisabled(update.is_translations_disabled()); diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index d55e8a4e6..680627e0e 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -132,6 +132,7 @@ public: }; mutable int lastParticipantsStatus = LastParticipantsUpToDate; int lastParticipantsCount = 0; + int boostsApplied = 0; private: ChatData *_migratedFrom = nullptr; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 6308e01a3..006d2f87c 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -139,6 +139,7 @@ struct HistoryItem::CreateConfig { UserId viaBotId = 0; int viewsCount = -1; int forwardsCount = -1; + int boostsApplied = 0; QString postAuthor; MsgId originalId = 0; @@ -352,6 +353,8 @@ HistoryItem::HistoryItem( FlagsFromMTP(id, data.vflags().v, localFlags), data.vdate().v, data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) { + _boostsApplied = data.vfrom_boosts_applied().value_or_empty(); + const auto media = data.vmedia(); const auto checked = media ? CheckMessageMedia(*media) @@ -1474,8 +1477,9 @@ void HistoryItem::returnSavedMedia() { return; } const auto wasGrouped = history()->owner().groups().isGrouped(this); - _media = std::move(_savedLocalEditMediaData->media); - setText(_savedLocalEditMediaData->text); + const auto data = Get(); + _media = std::move(data->media); + setText(data->text); clearSavedMedia(); if (wasGrouped) { history()->owner().groups().refreshMessage(this, true); @@ -1488,19 +1492,18 @@ void HistoryItem::returnSavedMedia() { void HistoryItem::savePreviousMedia() { Expects(_media != nullptr); - using Data = SavedMediaData; - _savedLocalEditMediaData = std::make_unique(Data{ - .text = originalText(), - .media = _media->clone(this), - }); + AddComponents(HistoryMessageSavedMediaData::Bit()); + const auto data = Get(); + data->text = originalText(); + data->media = _media->clone(this); } bool HistoryItem::isEditingMedia() const { - return _savedLocalEditMediaData != nullptr; + return Has(); } void HistoryItem::clearSavedMedia() { - _savedLocalEditMediaData = nullptr; + RemoveComponents(HistoryMessageSavedMediaData::Bit()); } bool HistoryItem::definesReplyKeyboard() const { @@ -1652,9 +1655,10 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { // } //} + const auto editingMedia = isEditingMedia(); const auto updatingSavedLocalEdit = !edition.savePreviousMedia - && (_savedLocalEditMediaData != nullptr); - if (!_savedLocalEditMediaData && edition.savePreviousMedia) { + && editingMedia; + if (!editingMedia && edition.savePreviousMedia) { savePreviousMedia(); } Assert(!updatingSavedLocalEdit || !isLocalUpdateMedia()); @@ -1683,7 +1687,7 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { setReplyMarkup(base::take(edition.replyMarkup)); } if (updatingSavedLocalEdit) { - _savedLocalEditMediaData->media = edition.mtpMedia + Get()->media = edition.mtpMedia ? CreateMedia(this, *edition.mtpMedia) : nullptr; } else { @@ -1700,13 +1704,13 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { setForwardsCount(edition.forwards); } const auto &checkedMedia = updatingSavedLocalEdit - ? _savedLocalEditMediaData->media + ? Get()->media : _media; auto updatedText = checkedMedia ? edition.textWithEntities : EnsureNonEmpty(edition.textWithEntities); if (updatingSavedLocalEdit) { - _savedLocalEditMediaData->text = std::move(updatedText); + Get()->text = std::move(updatedText); } else { setText(std::move(updatedText)); addToSharedMediaIndex(); @@ -1866,7 +1870,7 @@ void HistoryItem::applySentMessage( void HistoryItem::updateSentContent( const TextWithEntities &textWithEntities, const MTPMessageMedia *media) { - if (_savedLocalEditMediaData) { + if (isEditingMedia()) { return; } setText(textWithEntities); @@ -1998,10 +2002,9 @@ void HistoryItem::destroyHistoryEntry() { } Storage::SharedMediaTypesMask HistoryItem::sharedMediaTypes() const { - auto result = Storage::SharedMediaTypesMask {}; - const auto media = _savedLocalEditMediaData - ? _savedLocalEditMediaData->media.get() - : _media.get(); + auto result = Storage::SharedMediaTypesMask{}; + const auto saved = Get(); + const auto media = saved ? saved->media.get() : _media.get(); if (media) { result.set(media->sharedMediaTypes()); } @@ -3403,6 +3406,12 @@ void HistoryItem::createComponents(CreateConfig &&config) { } else { _flags &= ~MessageFlag::HasReplyMarkup; } + + if (out() && isSending()) { + if (const auto channel = _history->peer->asMegagroup()) { + _boostsApplied = channel->mgInfo->boostsApplied; + } + } } bool HistoryItem::checkRepliesPts( diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 3c5df4f25..0525e5c12 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -21,6 +21,7 @@ struct HistoryMessageMarkupData; struct HistoryMessageReplyMarkup; struct HistoryMessageTranslation; struct HistoryMessageForwarded; +struct HistoryMessageSavedMediaData; struct HistoryServiceDependentData; enum class HistorySelfDestructType; struct PreparedServiceText; @@ -536,16 +537,15 @@ public: return _ttlDestroyAt; } + [[nodiscard]] int boostsApplied() const { + return _boostsApplied; + } + MsgId id; private: struct CreateConfig; - struct SavedMediaData { - TextWithEntities text; - std::unique_ptr media; - }; - HistoryItem( not_null history, MsgId id, @@ -655,13 +655,13 @@ private: TextWithEntities _text; - std::unique_ptr _savedLocalEditMediaData; std::unique_ptr _media; std::unique_ptr _reactions; crl::time _reactionsLastRefreshed = 0; TimeId _date = 0; TimeId _ttlDestroyAt = 0; + int _boostsApplied = 0; HistoryView::Element *_mainView = nullptr; MessageGroupId _groupId = MessageGroupId(); @@ -672,3 +672,5 @@ private: friend class HistoryView::ServiceMessagePainter; }; + +constexpr auto kSize = int(sizeof(HistoryItem)); diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index bd0eb6a9d..4d60ff9a3 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -149,6 +149,11 @@ struct HistoryMessageForwarded : public RuntimeComponent { + TextWithEntities text; + std::unique_ptr media; +}; + struct HistoryMessageSaved : public RuntimeComponent { Data::SavedSublist *sublist = nullptr; }; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 3db8124cb..12ac5e714 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -454,19 +454,20 @@ void Message::setReactions(std::unique_ptr list) { } void Message::refreshRightBadge() { + const auto item = data(); const auto text = [&] { - if (data()->isDiscussionPost()) { + if (item->isDiscussionPost()) { return (delegate()->elementContext() == Context::Replies) ? QString() : tr::lng_channel_badge(tr::now); - } else if (data()->author()->isMegagroup()) { - if (const auto msgsigned = data()->Get()) { + } else if (item->author()->isMegagroup()) { + if (const auto msgsigned = item->Get()) { Assert(msgsigned->isAnonymousRank); return msgsigned->postAuthor; } } - const auto channel = data()->history()->peer->asMegagroup(); - const auto user = data()->author()->asUser(); + const auto channel = item->history()->peer->asMegagroup(); + const auto user = item->author()->asUser(); if (!channel || !user) { return QString(); } @@ -485,13 +486,41 @@ void Message::refreshRightBadge() { ? tr::lng_admin_badge(tr::now) : QString(); }(); - const auto badge = text.isEmpty() - ? delegate()->elementAuthorRank(this) - : TextUtilities::RemoveEmoji(TextUtilities::SingleLine(text)); - if (badge.isEmpty()) { + auto badge = TextWithEntities{ + (text.isEmpty() + ? delegate()->elementAuthorRank(this) + : TextUtilities::RemoveEmoji(TextUtilities::SingleLine(text))) + }; + _rightBadgeHasBoosts = 0; + if (const auto boosts = item->boostsApplied()) { + _rightBadgeHasBoosts = 1; + + const auto many = (boosts > 1); + const auto &icon = many + ? st::boostsMessageIcon + : st::boostMessageIcon; + const auto padding = many + ? st::boostsMessageIconPadding + : st::boostMessageIconPadding; + const auto owner = &item->history()->owner(); + auto added = Ui::Text::SingleCustomEmoji( + owner->customEmojiManager().registerInternalEmoji(icon, padding) + ).append(many ? QString::number(boosts) : QString()); + badge.append(' ').append(Ui::Text::Colorized(added, 1)); + } + if (badge.empty()) { _rightBadge.clear(); } else { - _rightBadge.setText(st::defaultTextStyle, badge); + const auto context = Core::MarkedTextContext{ + .session = &item->history()->session(), + .customEmojiRepaint = [] {}, + .customEmojiLoopLimit = 1, + }; + _rightBadge.setMarkedText( + st::defaultTextStyle, + badge, + Ui::NameTextOptions(), + context); } } @@ -1482,20 +1511,30 @@ void Message::paintFromName( } if (rightWidth) { p.setPen(stm->msgDateFg); - p.setFont(ClickHandler::showAsActive(_fastReplyLink) - ? st::msgFont->underline() - : st::msgFont); if (replyWidth) { + p.setFont(ClickHandler::showAsActive(_fastReplyLink) + ? st::msgFont->underline() + : st::msgFont); p.drawText( trect.left() + trect.width() - rightWidth, trect.top() + st::msgFont->ascent, FastReplyText()); } else { - _rightBadge.draw( - p, - trect.left() + trect.width() - rightWidth, - trect.top(), - rightWidth); + const auto shift = QPoint(trect.width() - rightWidth, 0); + const auto pen = !_rightBadgeHasBoosts + ? QPen() + : !context.outbg + ? QPen(FromNameFg(context, colorIndex())) + : stm->msgServiceFg->p; + auto colored = std::array{ + { { &pen, &pen } }, + }; + _rightBadge.draw(p, { + .position = trect.topLeft() + shift, + .availableWidth = rightWidth, + .colors = colored, + .now = context.now, + }); } } trect.setY(trect.y() + st::msgNameFont->height); diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 1f13056e8..62ab6c02b 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -305,9 +305,10 @@ private: mutable std::unique_ptr _fromNameStatus; Ui::Text::String _rightBadge; mutable int _fromNameVersion = 0; - uint32 _bubbleWidthLimit : 30 = 0; + uint32 _bubbleWidthLimit : 29 = 0; uint32 _invertMedia : 1 = 0; uint32 _hideReply : 1 = 0; + uint32 _rightBadgeHasBoosts : 1 = 0; BottomInfo _bottomInfo; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 6ec3aeb04..2a2733f2a 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1042,3 +1042,8 @@ chatSimilarSkip: 12px; premiumRequiredWidth: 186px; premiumRequiredIcon: icon{{ "chat/large_lockedchat", msgServiceFg }}; premiumRequiredCircle: 60px; + +boostMessageIcon: icon {{ "stories/boost_mini", windowFg }}; +boostMessageIconPadding: margins(0px, 2px, 0px, 0px); +boostsMessageIcon: icon {{ "stories/boosts_mini", windowFg }}; +boostsMessageIconPadding: margins(0px, 2px, 0px, 0px);