diff --git a/Telegram/Resources/icons/stories/boosts_mini.png b/Telegram/Resources/icons/stories/boosts_mini.png new file mode 100644 index 000000000..80c667ecc Binary files /dev/null and b/Telegram/Resources/icons/stories/boosts_mini.png differ 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 000000000..95a806396 Binary files /dev/null and b/Telegram/Resources/icons/stories/boosts_mini@2x.png differ 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 000000000..423de8209 Binary files /dev/null and b/Telegram/Resources/icons/stories/boosts_mini@3x.png differ 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);