diff --git a/Telegram/Resources/icons/history_comments.png b/Telegram/Resources/icons/history_comments.png new file mode 100644 index 0000000000..068e9c15d4 Binary files /dev/null and b/Telegram/Resources/icons/history_comments.png differ diff --git a/Telegram/Resources/icons/history_comments@2x.png b/Telegram/Resources/icons/history_comments@2x.png new file mode 100644 index 0000000000..f67f3c9a00 Binary files /dev/null and b/Telegram/Resources/icons/history_comments@2x.png differ diff --git a/Telegram/Resources/icons/history_comments@3x.png b/Telegram/Resources/icons/history_comments@3x.png new file mode 100644 index 0000000000..b19d206cdb Binary files /dev/null and b/Telegram/Resources/icons/history_comments@3x.png differ diff --git a/Telegram/Resources/icons/history_comments_open.png b/Telegram/Resources/icons/history_comments_open.png new file mode 100644 index 0000000000..4edb30acaf Binary files /dev/null and b/Telegram/Resources/icons/history_comments_open.png differ diff --git a/Telegram/Resources/icons/history_comments_open@2x.png b/Telegram/Resources/icons/history_comments_open@2x.png new file mode 100644 index 0000000000..75bd3b0cef Binary files /dev/null and b/Telegram/Resources/icons/history_comments_open@2x.png differ diff --git a/Telegram/Resources/icons/history_comments_open@3x.png b/Telegram/Resources/icons/history_comments_open@3x.png new file mode 100644 index 0000000000..94f7cecf56 Binary files /dev/null and b/Telegram/Resources/icons/history_comments_open@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f037f64011..984799c915 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1351,6 +1351,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_replies_header#other" = "{count} replies"; "lng_replies_header_none" = "No replies"; "lng_replies_send_placeholder" = "Reply"; +"lng_comments_view#one" = "View {count} Comment"; +"lng_comments_view#other" = "View {count} Comments"; +"lng_comments_view_thread" = "Leave a Comment"; +"lng_comments_header#one" = "{count} comment"; +"lng_comments_header#other" = "{count} comments"; +"lng_comments_header_none" = "No comments"; +"lng_comments_open_count#one" = "{count} comment"; +"lng_comments_open_count#other" = "{count} comments"; +"lng_comments_open_none" = "Leave a comment"; +"lng_comments_send_placeholder" = "Comment"; "lng_archived_name" = "Archived chats"; "lng_archived_add" = "Archive"; diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index f5d4a3cbcf..90f69371f0 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -145,6 +145,9 @@ const ChannelLocation *ChannelData::getLocation() const { void ChannelData::setLinkedChat(ChannelData *linked) { if (_linkedChat != linked) { _linkedChat = linked; + if (const auto history = owner().historyLoaded(this)) { + history->forceFullResize(); + } session().changes().peerUpdated(this, UpdateFlag::ChannelLinkedChat); } } diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 90c58d711c..cde9558783 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1663,11 +1663,7 @@ bool Session::checkEntitiesAndViewsUpdate(const MTPDmessage &data) { existing->updateForwardedInfo(data.vfwd_from()); existing->setViewsCount(data.vviews().value_or(-1)); if (const auto replies = data.vreplies()) { - replies->match([&](const MTPDmessageReplies &data) { - existing->setRepliesCount( - data.vreplies().v, - data.vreplies_pts().v); - }); + existing->setReplies(*replies); } existing->setForwardsCount(data.vforwards().value_or(-1)); if (const auto reply = data.vreply_to()) { diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style index 2b794c8ed6..f28cdc6667 100644 --- a/Telegram/SourceFiles/history/history.style +++ b/Telegram/SourceFiles/history/history.style @@ -615,6 +615,14 @@ historyPollOutChosenSelected: icon {{ "poll_select_check", historyFileOutIconFgS historyPollInChosen: icon {{ "poll_select_check", historyFileInIconFg }}; historyPollInChosenSelected: icon {{ "poll_select_check", historyFileInIconFgSelected }}; +historyCommentsButtonHeight: 40px; +historyCommentsSkipLeft: 9px; +historyCommentsSkipText: 10px; +historyCommentsUserpicSize: 25px; +historyCommentsUserpicStroke: 2px; +historyCommentsUserpicOverlap: 6px; +historyCommentsSkipRight: 8px; + boxAttachEmoji: IconButton(historyAttachEmoji) { width: 30px; height: 30px; @@ -658,6 +666,15 @@ historyQuizTimerInSelected: icon {{ "quiz_timer", msgFileThumbLinkInFgSelected } historyQuizTimerOut: icon {{ "quiz_timer", msgFileThumbLinkOutFg }}; historyQuizTimerOutSelected: icon {{ "quiz_timer", msgFileThumbLinkOutFgSelected }}; +historyCommentsIn: icon {{ "history_comments", msgFileThumbLinkInFg }}; +historyCommentsInSelected: icon {{ "history_comments", msgFileThumbLinkInFgSelected }}; +historyCommentsOut: icon {{ "history_comments", msgFileThumbLinkOutFg }}; +historyCommentsOutSelected: icon {{ "history_comments", msgFileThumbLinkOutFgSelected }}; +historyCommentsOpenIn: icon {{ "history_comments_open", msgFileThumbLinkInFg }}; +historyCommentsOpenInSelected: icon {{ "history_comments_open", msgFileThumbLinkInFgSelected }}; +historyCommentsOpenOut: icon {{ "history_comments_open", msgFileThumbLinkOutFg }}; +historyCommentsOpenOutSelected: icon {{ "history_comments_open", msgFileThumbLinkOutFgSelected }}; + historySlowmodeCounterMargins: margins(0px, 0px, 10px, 0px); largeEmojiSize: 36px; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 2e35034914..1daacd11c2 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -1542,12 +1542,18 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }); } if (IsServerMsgId(item->id) && item->repliesCount() > 0) { - _menu->addAction(tr::lng_replies_view(tr::now, lt_count, item->repliesCount()), [=] { + const auto &phrase = item->repliesAreComments() + ? tr::lng_comments_view + : tr::lng_replies_view; + _menu->addAction(phrase(tr::now, lt_count, item->repliesCount()), [=] { controller->showSection( HistoryView::RepliesMemento(_history, itemId.msg)); }); } else if (const auto replyToTop = item->replyToTop()) { - _menu->addAction(tr::lng_replies_view_thread(tr::now), [=] { + const auto &phrase = item->repliesAreComments() + ? tr::lng_comments_view_thread + : tr::lng_replies_view_thread; + _menu->addAction(phrase(tr::now), [=] { controller->showSection( HistoryView::RepliesMemento(_history, replyToTop)); }); diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 7fa9dc7eef..91fe98fb29 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -195,6 +195,9 @@ public: [[nodiscard]] virtual int repliesCount() const { return 0; } + [[nodiscard]] virtual bool repliesAreComments() const { + return false; + } [[nodiscard]] virtual bool needCheck() const; @@ -252,7 +255,9 @@ public: } virtual void setForwardsCount(int count) { } - virtual void setRepliesCount(int count, int pts) { + virtual void setReplies(const MTPMessageReplies &data) { + } + virtual void changeRepliesCount(int delta, UserId replier) { } virtual void setReplyToTop(MsgId replyToTop) { } diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 06246ee46c..bd9296b67b 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -34,10 +34,16 @@ struct HistoryMessageVia : public RuntimeComponent { - QString text; - int textWidth = 0; - int views = -1; - int replies = 0; + struct Part { + QString text; + int textWidth = 0; + int count = -1; + }; + std::vector recentRepliers; + Part views; + Part replies; + ChannelId repliesChannelId = 0; + static constexpr auto kMaxRecentRepliers = 3; }; struct HistoryMessageSigned : public RuntimeComponent { diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index a178441984..3c04f0b3cf 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -409,7 +409,6 @@ struct HistoryMessage::CreateConfig { MsgId replyToTop = 0; UserId viaBotId = 0; int viewsCount = -1; - int repliesCount = 0; QString author; PeerId senderOriginal = 0; QString senderNameOriginal; @@ -422,6 +421,7 @@ struct HistoryMessage::CreateConfig { TimeId editDate = 0; // For messages created from MTP structs. + const MTPMessageReplies *mtpReplies = nullptr; const MTPReplyMarkup *mtpMarkup = nullptr; // For messages created from existing messages (forwarded). @@ -474,11 +474,7 @@ HistoryMessage::HistoryMessage( } config.viaBotId = data.vvia_bot_id().value_or_empty(); config.viewsCount = data.vviews().value_or(-1); - if (const auto replies = data.vreplies()) { - replies->match([&](const MTPDmessageReplies &data) { - config.repliesCount = data.vreplies().v; - }); - } + config.mtpReplies = data.vreplies(); config.mtpMarkup = data.vreply_markup(); config.editDate = data.vedit_date().value_or_empty(); config.author = qs(data.vpost_author().value_or_empty()); @@ -744,18 +740,45 @@ void HistoryMessage::createComponentsHelper( int HistoryMessage::viewsCount() const { if (const auto views = Get()) { - return views->views; + return std::max(views->views.count, 0); } return HistoryItem::viewsCount(); } int HistoryMessage::repliesCount() const { if (const auto views = Get()) { - return views->replies; + if (views->repliesChannelId) { + if (const auto channel = history()->peer->asChannel()) { + const auto linked = channel->linkedChat(); + if (!linked || linked->bareId() != views->repliesChannelId) { + return 0; + } + } else { + return 0; + } + } + return std::max(views->replies.count, 0); } return HistoryItem::repliesCount(); } +bool HistoryMessage::repliesAreComments() const { + if (const auto views = Get()) { + if (!views->repliesChannelId) { + return false; + } else if (const auto channel = history()->peer->asChannel()) { + const auto linked = channel->linkedChat(); + if (!linked || linked->bareId() != views->repliesChannelId) { + return false; + } + } else { + return false; + } + return true; + } + return HistoryItem::repliesAreComments(); +} + bool HistoryMessage::updateDependencyItem() { if (const auto reply = Get()) { const auto documentId = reply->replyToDocumentId; @@ -848,7 +871,7 @@ void HistoryMessage::createComponents(const CreateConfig &config) { if (config.viaBotId) { mask |= HistoryMessageVia::Bit(); } - if (config.viewsCount >= 0 || config.repliesCount > 0) { + if (config.viewsCount >= 0 || config.mtpReplies) { mask |= HistoryMessageViews::Bit(); } if (!config.author.isEmpty()) { @@ -886,8 +909,10 @@ void HistoryMessage::createComponents(const CreateConfig &config) { via->create(&history()->owner(), config.viaBotId); } if (const auto views = Get()) { - views->views = config.viewsCount; - views->replies = config.repliesCount; + setViewsCount(config.viewsCount); + if (config.mtpReplies) { + setReplies(*config.mtpReplies); + } } if (const auto edited = Get()) { edited->date = config.editDate; @@ -1400,20 +1425,20 @@ bool HistoryMessage::textHasLinks() const { void HistoryMessage::setViewsCount(int count) { const auto views = Get(); if (!views - || views->views == count - || (count >= 0 && views->views > count)) { + || views->views.count == count + || (count >= 0 && views->views.count > count)) { return; } - views->views = count; - views->text = (views->views > 0) - ? Lang::FormatCountToShort(views->views).string - : QString("1"); - const auto was = views->textWidth; - views->textWidth = views->text.isEmpty() + views->views.count = count; + views->views.text = Lang::FormatCountToShort( + std::max(views->views.count, 1) + ).string; + const auto was = views->views.textWidth; + views->views.textWidth = views->views.text.isEmpty() ? 0 - : st::msgDateFont->width(views->text); - if (was == views->textWidth) { + : st::msgDateFont->width(views->views.text); + if (was == views->views.textWidth) { history()->owner().requestItemRepaint(this); } else { history()->owner().requestItemResize(this); @@ -1423,39 +1448,88 @@ void HistoryMessage::setViewsCount(int count) { void HistoryMessage::setForwardsCount(int count) { } -void HistoryMessage::setRepliesCount(int count, int pts) { - auto views = Get(); - if (!views) { - if (!count) { +void HistoryMessage::setReplies(const MTPMessageReplies &data) { + data.match([&](const MTPDmessageReplies &data) { + auto views = Get(); + if (!views) { + AddComponents(HistoryMessageViews::Bit()); + views = Get(); + } + const auto repliers = [&] { + auto result = std::vector(); + if (const auto list = data.vrecent_repliers()) { + result.reserve(list->v.size()); + for (const auto &id : list->v) { + result.push_back(id.v); + } + } + return result; + }(); + const auto count = data.vreplies().v; + const auto channelId = data.vchannel_id().value_or_empty(); + const auto countChanged = (views->replies.count != count); + const auto channelChanged = (views->repliesChannelId != channelId); + const auto recentChanged = (views->recentRepliers != repliers); + if (!countChanged && !channelChanged && !recentChanged) { return; } - AddComponents(HistoryMessageViews::Bit()); - views = Get(); - } - if (views->replies == count) { - return; - } - views->replies = count; - if (views->views >= 0) { - return; - } else if (!views->replies) { - RemoveComponents(HistoryMessageViews::Bit()); - history()->owner().requestItemResize(this); - return; - } + views->replies.count = count; + if (recentChanged) { + views->recentRepliers = repliers; + } + views->repliesChannelId = channelId; + refreshRepliesText(views, channelChanged); + }); +} - views->text = (views->replies > 0) - ? Lang::FormatCountToShort(views->replies).string - : QString(); - const auto was = views->textWidth; - views->textWidth = views->text.isEmpty() - ? 0 - : st::msgDateFont->width(views->text); - if (was == views->textWidth) { - history()->owner().requestItemRepaint(this); +void HistoryMessage::refreshRepliesText( + not_null views, + bool forceResize) { + const auto was = views->replies.textWidth; + if (views->repliesChannelId) { + views->replies.text = (views->replies.count > 0) + ? tr::lng_comments_open_count( + tr::now, + lt_count_short, + views->replies.count) + : tr::lng_comments_open_none(tr::now); + views->replies.textWidth = st::semiboldFont->width( + views->replies.text); } else { - history()->owner().requestItemResize(this); + views->replies.text = (views->replies.count > 0) + ? Lang::FormatCountToShort(views->replies.count).string + : QString(); + views->replies.textWidth = views->replies.text.isEmpty() + ? 0 + : st::msgDateFont->width(views->replies.text); } + if (forceResize || views->replies.textWidth != was) { + history()->owner().requestItemResize(this); + } else { + history()->owner().requestItemRepaint(this); + } +} + +void HistoryMessage::changeRepliesCount(int delta, UserId replier) { + const auto views = Get(); + const auto limit = HistoryMessageViews::kMaxRecentRepliers; + if (!views || views->replies.count < 0) { + return; + } + views->replies.count = std::max(views->replies.count + delta, 0); + if (replier && views->repliesChannelId) { + if (delta < 0) { + views->recentRepliers.erase( + ranges::remove(views->recentRepliers, replier), + end(views->recentRepliers)); + } else if (!ranges::contains(views->recentRepliers, replier)) { + views->recentRepliers.insert(views->recentRepliers.begin(), replier); + while (views->recentRepliers.size() > limit) { + views->recentRepliers.pop_back(); + } + } + } + refreshRepliesText(views); } void HistoryMessage::setReplyToTop(MsgId replyToTop) { @@ -1499,7 +1573,13 @@ void HistoryMessage::incrementReplyToTopCounter( channelId, reply->replyToTop()); if (top) { - top->setRepliesCount(top->repliesCount() + 1, 0); + if (const auto from = displayFrom()) { + if (const auto user = from->asUser()) { + top->changeRepliesCount(1, user->bareId()); + return; + } + } + top->changeRepliesCount(1, UserId()); } } } @@ -1513,8 +1593,14 @@ void HistoryMessage::decrementReplyToTopCounter( const auto top = history()->owner().message( channelId, reply->replyToTop()); - if (const auto replies = (top ? top->repliesCount() : 0)) { - top->setRepliesCount(replies - 1, 0); + if (top) { + if (const auto from = displayFrom()) { + if (const auto user = from->asUser()) { + top->changeRepliesCount(-1, user->bareId()); + return; + } + } + top->changeRepliesCount(-1, UserId()); } } } diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index 2cd0bb3096..924d1a733d 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -19,6 +19,7 @@ class Message; struct HistoryMessageEdited; struct HistoryMessageReply; +struct HistoryMessageViews; Fn HistoryDependentItemCallback( not_null item); @@ -133,7 +134,8 @@ public: void setViewsCount(int count) override; void setForwardsCount(int count) override; - void setRepliesCount(int count, int pts) override; + void setReplies(const MTPMessageReplies &data) override; + void changeRepliesCount(int delta, UserId replier) override; void setReplyToTop(MsgId replyToTop) override; void setRealId(MsgId newId) override; void incrementReplyToTopCounter() override; @@ -165,6 +167,7 @@ public: [[nodiscard]] int viewsCount() const override; [[nodiscard]] int repliesCount() const override; + [[nodiscard]] bool repliesAreComments() const override; bool updateDependencyItem() override; [[nodiscard]] MsgId dependencyMsgId() const override { return replyToId(); @@ -206,6 +209,9 @@ private: void setupForwardedComponent(const CreateConfig &config); void incrementReplyToTopCounter(not_null reply); void decrementReplyToTopCounter(not_null reply); + void refreshRepliesText( + not_null views, + bool forceResize = false); static void FillForwardedInfo( CreateConfig &config, diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 55051f4246..444ccc9c4b 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -624,8 +624,12 @@ auto Element::verticalRepaintRange() const -> VerticalRepaintRange { }; } +bool Element::hasHeavyPart() const { + return false; +} + void Element::checkHeavyPart() { - if (!_media || !_media->hasHeavyPart()) { + if (!hasHeavyPart() && (!_media || !_media->hasHeavyPart())) { history()->owner().unregisterHeavyViewPart(this); } } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 77d547b3e4..76aa46964e 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -278,8 +278,9 @@ public: }; [[nodiscard]] virtual VerticalRepaintRange verticalRepaintRange() const; + virtual bool hasHeavyPart() const; + virtual void unloadHeavyPart(); void checkHeavyPart(); - void unloadHeavyPart(); // Legacy blocks structure. HistoryBlock *block(); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index acb5ef4df0..ab746e30f2 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -12,7 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_message.h" #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_web_page.h" +#include "history/view/history_view_replies_section.h" #include "history/history.h" +#include "ui/effects/ripple_animation.h" #include "core/application.h" #include "core/core_settings.h" #include "ui/toast/toast.h" @@ -189,6 +191,19 @@ style::color FromNameFg(PeerId peerId, bool selected) { } // namespace +struct Message::CommentsButton { + struct Userpic { + not_null user; + std::shared_ptr view; + InMemoryKey uniqueKey; + }; + std::unique_ptr ripple; + std::vector userpics; + QImage cachedUserpics; + ClickHandlerPtr link; + QPoint lastPoint; +}; + LogEntryOriginal::LogEntryOriginal() = default; LogEntryOriginal::LogEntryOriginal(LogEntryOriginal &&other) @@ -211,6 +226,13 @@ Message::Message( initPsa(); } +Message::~Message() { + if (_comments) { + _comments = nullptr; + checkHeavyPart(); + } +} + not_null Message::message() const { return static_cast(data().get()); } @@ -460,6 +482,9 @@ void Message::draw( auto displayTail = skipTail ? RectPart::None : (outbg && !Core::App().settings().chatWide()) ? RectPart::Right : RectPart::Left; PaintBubble(p, g, width(), selected, outbg, displayTail); + const auto gBubble = g; + paintCommentsButton(p, g, selected); + // Entry page is always a bubble bottom. auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); @@ -507,19 +532,28 @@ void Message::draw( : true); if (needDrawInfo) { drawInfo(p, g.left() + g.width(), g.top() + g.height(), 2 * g.left() + g.width(), selected, InfoDisplayType::Default); + if (g != gBubble) { + const auto o = p.opacity(); + p.setOpacity(0.3); + const auto color = selected + ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) + : (outbg ? st::msgOutDateFg : st::msgInDateFg); + p.fillRect(g.left(), g.top() + g.height() - st::lineWidth, g.width(), st::lineWidth, color); + p.setOpacity(o); + } } if (displayRightAction()) { - const auto fastShareSkip = snap( - (g.height() - st::historyFastShareSize) / 2, + const auto fastShareSkip = std::clamp( + (gBubble.height() - st::historyFastShareSize) / 2, 0, st::historyFastShareBottom); const auto fastShareLeft = g.left() + g.width() + st::historyFastShareLeft; - const auto fastShareTop = g.top() + g.height() - fastShareSkip - st::historyFastShareSize; + const auto fastShareTop = g.top() + gBubble.height() - fastShareSkip - st::historyFastShareSize; drawRightAction(p, fastShareLeft, fastShareTop, width()); } if (media) { - media->paintBubbleFireworks(p, g, ms); + media->paintBubbleFireworks(p, gBubble, ms); } } else if (media && media->isDisplayed()) { p.translate(g.topLeft()); @@ -540,6 +574,137 @@ void Message::draw( } } +void Message::paintCommentsButton( + Painter &p, + QRect &g, + bool selected) const { + if (!data()->repliesAreComments()) { + return; + } + if (!_comments) { + _comments = std::make_unique(); + history()->owner().registerHeavyViewPart(const_cast(this)); + } + const auto outbg = hasOutLayout(); + const auto views = data()->Get(); + Assert(views != nullptr); + + g.setHeight(g.height() - st::historyCommentsButtonHeight); + const auto top = g.top() + g.height(); + auto left = g.left(); + auto width = g.width(); + + if (_comments->ripple) { + p.setOpacity(st::historyPollRippleOpacity); + _comments->ripple->paint(p, left, top, width); + if (_comments->ripple->empty()) { + _comments->ripple.reset(); + } + p.setOpacity(1.); + } + + left += st::historyCommentsSkipLeft; + width -= st::historyCommentsSkipLeft + + st::historyCommentsSkipRight; + + const auto &open = outbg + ? (selected ? st::historyCommentsOpenOutSelected : st::historyCommentsOpenOut) + : (selected ? st::historyCommentsOpenInSelected : st::historyCommentsOpenIn); + open.paint(p, + left + width - open.width(), + top + (st::historyCommentsButtonHeight - open.height()) / 2, + width); + + if (views->recentRepliers.empty()) { + const auto &icon = outbg + ? (selected ? st::historyCommentsOutSelected : st::historyCommentsOut) + : (selected ? st::historyCommentsInSelected : st::historyCommentsIn); + icon.paint( + p, + left, + top + (st::historyCommentsButtonHeight - icon.height()) / 2, + width); + left += icon.width(); + } else { + auto &list = _comments->userpics; + const auto limit = HistoryMessageViews::kMaxRecentRepliers; + const auto count = std::min(int(views->recentRepliers.size()), limit); + const auto single = st::historyCommentsUserpicSize; + const auto shift = st::historyCommentsUserpicOverlap; + const auto regenerate = [&] { + if (list.size() != count) { + return true; + } + for (auto i = 0; i != count; ++i) { + auto &entry = list[i]; + const auto user = entry.user; + auto &view = entry.view; + const auto wasView = view.get(); + if (views->recentRepliers[i] != user->bareId() + || user->userpicUniqueKey(view) != entry.uniqueKey + || view.get() != wasView) { + return true; + } + } + return false; + }(); + if (regenerate) { + for (auto i = 0; i != count; ++i) { + const auto userId = views->recentRepliers[i]; + if (i == list.size()) { + list.push_back(CommentsButton::Userpic{ + history()->owner().user(userId) + }); + } else if (list[i].user->bareId() != userId) { + list[i].user = history()->owner().user(userId); + } + } + while (list.size() > count) { + list.pop_back(); + } + const auto width = single + (limit - 1) * (single - shift); + if (_comments->cachedUserpics.isNull()) { + _comments->cachedUserpics = QImage( + QSize(width, single) * cIntRetinaFactor(), + QImage::Format_ARGB32_Premultiplied); + } + _comments->cachedUserpics.fill(Qt::transparent); + auto q = Painter(&_comments->cachedUserpics); + auto hq = PainterHighQualityEnabler(q); + auto pen = QPen(Qt::transparent); + pen.setWidth(st::historyCommentsUserpicStroke); + q.setBrush(Qt::NoBrush); + q.setPen(pen); + auto x = (count - 1) * (single - shift); + for (auto i = count; i != 0;) { + auto &entry = list[--i]; + q.setCompositionMode(QPainter::CompositionMode_SourceOver); + entry.user->paintUserpic(q, entry.view, x, 0, single); + entry.uniqueKey = entry.user->userpicUniqueKey(entry.view); + q.setCompositionMode(QPainter::CompositionMode_Source); + q.drawEllipse(x, 0, single, single); + x -= single - shift; + } + } + p.drawImage( + left, + top + (st::historyCommentsButtonHeight - single) / 2, + _comments->cachedUserpics); + left += single + (count - 1) * (single - shift); + } + + left += st::historyCommentsSkipText; + p.setPen(outbg ? (selected ? st::msgFileThumbLinkOutFgSelected : st::msgFileThumbLinkOutFg) : (selected ? st::msgFileThumbLinkInFgSelected : st::msgFileThumbLinkInFg)); + p.setFont(st::semiboldFont); + + p.drawTextLeft( + left, + top + (st::historyCommentsButtonHeight - st::semiboldFont->height) / 2, + width, + views->replies.text, + views->replies.textWidth); +} + void Message::paintFromName( Painter &p, QRect &trect, @@ -733,7 +898,7 @@ void Message::paintText(Painter &p, QRect &trect, TextSelection selection) const } PointState Message::pointState(QPoint point) const { - const auto g = countGeometry(); + auto g = countGeometry(); if (g.width() < 1 || isHidden()) { return PointState::Outside; } @@ -752,6 +917,10 @@ PointState Message::pointState(QPoint point) const { auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); + if (item->repliesAreComments()) { + g.setHeight(g.height() - st::historyCommentsButtonHeight); + } + auto trect = g.marginsRemoved(st::msgPadding); if (mediaOnBottom) { trect.setHeight(trect.height() + st::msgPadding.bottom()); @@ -788,6 +957,65 @@ bool Message::displayFromPhoto() const { return hasFromPhoto() && !isAttachedToNext(); } +void Message::clickHandlerPressedChanged( + const ClickHandlerPtr &handler, + bool pressed) { + Element::clickHandlerPressedChanged(handler, pressed); + + if (!handler || !_comments) { + return; + } else if (handler == _comments->link) { + toggleCommentsButtonRipple(pressed); + } +} + +void Message::toggleCommentsButtonRipple(bool pressed) { + Expects(_comments != nullptr); + + if (!drawBubble()) { + return; + } else if (pressed) { + const auto g = countGeometry(); + const auto linkWidth = g.width(); + const auto linkHeight = st::historyCommentsButtonHeight; + if (!_comments->ripple) { + const auto drawMask = [&](QPainter &p) { + const auto radius = st::historyMessageRadius; + p.drawRoundedRect( + 0, + 0, + linkWidth, + linkHeight, + radius, + radius); + p.fillRect(0, 0, linkWidth, radius * 2, Qt::white); + }; + auto mask = Ui::RippleAnimation::maskByDrawer( + QSize(linkWidth, linkHeight), + false, + drawMask); + _comments->ripple = std::make_unique( + (hasOutLayout() + ? st::historyPollRippleOut + : st::historyPollRippleIn), + std::move(mask), + [=] { history()->owner().requestViewRepaint(this); }); + } + _comments->ripple->add(_comments->lastPoint); + } else if (_comments->ripple) { + _comments->ripple->lastStop(); + } +} + +bool Message::hasHeavyPart() const { + return _comments || Element::hasHeavyPart(); +} + +void Message::unloadHeavyPart() { + Element::unloadHeavyPart(); + _comments = nullptr; +} + bool Message::hasFromPhoto() const { if (isHidden()) { return false; @@ -842,6 +1070,11 @@ TextState Message::textState( auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); + const auto gBubble = g; + if (getStateCommentsButton(point, g, &result)) { + return result; + } + auto trect = g.marginsRemoved(st::msgPadding); if (mediaOnBottom) { trect.setHeight(trect.height() + st::msgPadding.bottom()); @@ -913,11 +1146,11 @@ TextState Message::textState( checkForPointInTime(); if (displayRightAction()) { const auto fastShareSkip = snap( - (g.height() - st::historyFastShareSize) / 2, + (gBubble.height() - st::historyFastShareSize) / 2, 0, st::historyFastShareBottom); const auto fastShareLeft = g.left() + g.width() + st::historyFastShareLeft; - const auto fastShareTop = g.top() + g.height() - fastShareSkip - st::historyFastShareSize; + const auto fastShareTop = g.top() + gBubble.height() - fastShareSkip - st::historyFastShareSize; if (QRect( fastShareLeft, fastShareTop, @@ -943,6 +1176,34 @@ TextState Message::textState( return result; } +bool Message::getStateCommentsButton( + QPoint point, + QRect &g, + not_null outResult) const { + if (!_comments) { + return false; + } + g.setHeight(g.height() - st::historyCommentsButtonHeight); + if (QRect(g.left(), g.top() + g.height(), g.width(), st::historyCommentsButtonHeight).contains(point)) { + if (!_comments->link) { + const auto fullId = data()->fullId(); + _comments->link = std::make_shared([=] { + if (const auto window = App::wnd()) { + if (const auto controller = window->sessionController()) { + if (const auto item = controller->session().data().message(fullId)) { + controller->showSection( + HistoryView::RepliesMemento(item->history(), item->id)); + } + } + } + }); + } + outResult->link = _comments->link; + _comments->lastPoint = point - QPoint(g.left(), g.top() + g.height()); + } + return false; +} + bool Message::getStateFromName( QPoint point, QRect &trect, @@ -1344,32 +1605,67 @@ void Message::drawInfo( } if (auto views = item->Get()) { - const auto showReplies = /*(views->views < 0) && */(views->replies > 0); - auto icon = [&] { - if (item->id > 0) { - if (outbg) { + auto left = infoRight - infoW; + const auto iconTop = infoBottom + st::historyViewsTop; + const auto textTop = infoBottom - st::msgDateFont->descent; + if (views->replies.count > 0 && !views->repliesChannelId) { + auto icon = [&] { + if (item->id > 0) { + if (outbg) { + return &(invertedsprites + ? st::historyRepliesInvertedIcon + : selected + ? st::historyRepliesOutSelectedIcon + : st::historyRepliesOutIcon); + } return &(invertedsprites - ? (showReplies ? st::historyRepliesInvertedIcon : st::historyViewsInvertedIcon) + ? st::historyRepliesInvertedIcon : selected - ? (showReplies ? st::historyRepliesOutSelectedIcon : st::historyViewsOutSelectedIcon) - : (showReplies ? st::historyRepliesOutIcon : st::historyViewsOutIcon)); + ? st::historyRepliesInSelectedIcon + : st::historyRepliesInIcon); } return &(invertedsprites - ? (showReplies ? st::historyRepliesInvertedIcon : st::historyViewsInvertedIcon) - : selected - ? (showReplies ? st::historyRepliesInSelectedIcon : st::historyViewsInSelectedIcon) - : (showReplies ? st::historyRepliesInIcon : st::historyViewsInIcon)); + ? st::historyViewsSendingInvertedIcon + : st::historyViewsSendingIcon); + }(); + if (item->id > 0) { + icon->paint(p, left, iconTop, width); + p.drawText(left + st::historyViewsWidth, textTop, views->replies.text); + } else if (!outbg && views->views.count < 0) { // sending outbg icon will be painted below + auto iconSkip = st::historyViewsSpace + views->replies.textWidth; + icon->paint(p, left + iconSkip, iconTop, width); + } + left += st::historyViewsSpace + + views->replies.textWidth + + st::historyViewsWidth; + } + if (views->views.count >= 0) { + auto icon = [&] { + if (item->id > 0) { + if (outbg) { + return &(invertedsprites + ? st::historyViewsInvertedIcon + : selected + ? st::historyViewsOutSelectedIcon + : st::historyViewsOutIcon); + } + return &(invertedsprites + ? st::historyViewsInvertedIcon + : selected + ? st::historyViewsInSelectedIcon + : st::historyViewsInIcon); + } + return &(invertedsprites + ? st::historyViewsSendingInvertedIcon + : st::historyViewsSendingIcon); + }(); + if (item->id > 0) { + icon->paint(p, left, iconTop, width); + p.drawText(left + st::historyViewsWidth, textTop, views->views.text); + } else if (!outbg) { // sending outbg icon will be painted below + auto iconSkip = st::historyViewsSpace + views->views.textWidth; + icon->paint(p, left + iconSkip, iconTop, width); } - return &(invertedsprites - ? st::historyViewsSendingInvertedIcon - : st::historyViewsSendingIcon); - }(); - if (item->id > 0) { - icon->paint(p, infoRight - infoW, infoBottom + st::historyViewsTop, width); - p.drawText(infoRight - infoW + st::historyViewsWidth, infoBottom - st::msgDateFont->descent, views->text); - } else if (!outbg) { // sending outbg icon will be painted below - auto iconSkip = st::historyViewsSpace + views->textWidth; - icon->paint(p, infoRight - infoW + iconSkip, infoBottom + st::historyViewsTop, width); } } else if (item->id < 0 && item->history()->peer->isSelf() && !outbg) { auto icon = &(invertedsprites ? st::historyViewsSendingInvertedIcon : st::historyViewsSendingIcon); @@ -1424,9 +1720,16 @@ int Message::infoWidth() const { const auto item = message(); auto result = item->_timeWidth; if (auto views = item->Get()) { - result += st::historyViewsSpace - + views->textWidth - + st::historyViewsWidth; + if (views->views.count >= 0) { + result += st::historyViewsSpace + + views->views.textWidth + + st::historyViewsWidth; + } + if (views->replies.count > 0 && !views->repliesChannelId) { + result += st::historyViewsSpace + + views->replies.textWidth + + st::historyViewsWidth; + } } else if (item->id < 0 && item->history()->peer->isSelf()) { if (!hasOutLayout()) { result += st::historySendStateSpace; @@ -1465,7 +1768,12 @@ int Message::timeLeft() const { const auto item = message(); auto result = 0; if (auto views = item->Get()) { - result += st::historyViewsSpace + views->textWidth + st::historyViewsWidth; + if (views->views.count >= 0) { + result += st::historyViewsSpace + views->views.textWidth + st::historyViewsWidth; + } + if (views->replies.count > 0 && !views->repliesChannelId) { + result += st::historyViewsSpace + views->replies.textWidth + st::historyViewsWidth; + } } else if (item->id < 0 && item->history()->peer->isSelf()) { if (!hasOutLayout()) { result += st::historySendStateSpace; @@ -1950,6 +2258,10 @@ int Message::resizeContentGetHeight(int newWidth) { reply->resize(contentWidth - st::msgPadding.left() - st::msgPadding.right()); newHeight += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); } + + if (item->repliesAreComments()) { + newHeight += st::historyCommentsButtonHeight; + } } else if (mediaDisplayed) { newHeight = media->height(); } else { @@ -2007,18 +2319,6 @@ void Message::initTime() { item->_timeText = dateTime().toString(cTimeFormat()); item->_timeWidth = st::msgDateFont->width(item->_timeText); } - if (const auto views = item->Get()) { - views->text = (views->views > 0) - ? Lang::FormatCountToShort(views->views).string - : (views->views < 0) - ? (views->replies > 0 - ? Lang::FormatCountToShort(views->replies).string - : QString()) - : QString("1"); - views->textWidth = views->text.isEmpty() - ? 0 - : st::msgDateFont->width(views->text); - } if (item->_text.hasSkipBlock()) { if (item->_text.updateSkipBlock(skipBlockWidth(), skipBlockHeight())) { item->_textWidth = -1; diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 537b8f344a..45b36d86db 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -43,6 +43,11 @@ public: not_null delegate, not_null data, Element *replacing); + ~Message(); + + void clickHandlerPressedChanged( + const ClickHandlerPtr &handler, + bool pressed) override; int marginTop() const override; int marginBottom() const override; @@ -73,6 +78,9 @@ public: TextSelection selection, TextSelectType type) const override; + bool hasHeavyPart() const override; + void unloadHeavyPart() override; + // hasFromPhoto() returns true even if we don't display the photo // but we need to skip a place at the left side for this photo bool hasFromPhoto() const override; @@ -103,6 +111,8 @@ protected: void refreshDataIdHook() override; private: + struct CommentsButton; + not_null message() const; void initLogEntryOriginal(); @@ -115,6 +125,9 @@ private: [[nodiscard]] TextSelection unskipTextSelection( TextSelection selection) const; + void toggleCommentsButtonRipple(bool pressed); + + void paintCommentsButton(Painter &p, QRect &g, bool selected) const; void paintFromName(Painter &p, QRect &trect, bool selected) const; void paintForwardedInfo(Painter &p, QRect &trect, bool selected) const; void paintReplyInfo(Painter &p, QRect &trect, bool selected) const; @@ -122,6 +135,10 @@ private: void paintViaBotIdInfo(Painter &p, QRect &trect, bool selected) const; void paintText(Painter &p, QRect &trect, TextSelection selection) const; + bool getStateCommentsButton( + QPoint point, + QRect &g, + not_null outResult) const; bool getStateFromName( QPoint point, QRect &trect, @@ -170,6 +187,7 @@ private: mutable ClickHandlerPtr _rightActionLink; mutable ClickHandlerPtr _fastReplyLink; + mutable std::unique_ptr _comments; int _bubbleWidthLimit = 0; }; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 2efccb5755..d3e90ee577 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -105,6 +105,8 @@ RepliesWidget::RepliesWidget( : Window::SectionWidget(parent, controller) , _history(history) , _rootId(rootId) +, _root(lookupRoot()) +, _areComments(computeAreComments()) , _scroll(this, st::historyScroll, false) , _topBar(this, controller) , _topBarShadow(this) @@ -113,6 +115,8 @@ RepliesWidget::RepliesWidget( controller, ComposeControls::Mode::Normal)) , _scrollDown(_scroll, st::historyToDown) { + setupRoot(); + _topBar->setActiveChat(_history, TopBarWidget::Section::Replies); _topBar->move(0, 0); @@ -179,6 +183,33 @@ RepliesWidget::RepliesWidget( RepliesWidget::~RepliesWidget() = default; +void RepliesWidget::setupRoot() { + if (_root) { + refreshRootView(); + } else { + const auto channel = _history->peer->asChannel(); + const auto done = crl::guard(this, [=](ChannelData*, MsgId) { + _root = lookupRoot(); + if (_root) { + refreshRootView(); + _areComments = computeAreComments(); + } + }); + _history->session().api().requestMessageData(channel, _rootId, done); + } +} + +void RepliesWidget::refreshRootView() { +} + +HistoryItem *RepliesWidget::lookupRoot() const { + return _history->owner().message(_history->channelId(), _rootId); +} + +bool RepliesWidget::computeAreComments() const { + return _root && _root->isDiscussionPost(); +} + void RepliesWidget::setupComposeControls() { _composeControls->setHistory(_history); @@ -246,7 +277,7 @@ void RepliesWidget::setupComposeControls() { ) | rpl::start_with_next([=](not_null e) { if (e->key() == Qt::Key_Up) { if (!_composeControls->isEditingMessage()) { - // #TODO replies + // #TODO replies edit last sent message //auto &messages = session().data().scheduledMessages(); //if (const auto item = messages.lastSentMessage(_history)) { // _inner->editMessageRequestNotify(item->fullId()); @@ -1064,13 +1095,19 @@ void RepliesWidget::restoreState(not_null memento) { rpl::single( tr::lng_contacts_loading() - ) | rpl::then(_replies->fullCount( - ) | rpl::map([=](int count) { + ) | rpl::then(rpl::combine( + _replies->fullCount(), + _areComments.value() + ) | rpl::map([=](int count, bool areComments) { return count - ? tr::lng_replies_header( - lt_count, - rpl::single(count) | tr::to_count()) - : tr::lng_replies_header_none(); + ? (areComments + ? tr::lng_comments_header + : tr::lng_replies_header)( + lt_count, + rpl::single(count) | tr::to_count()) + : (areComments + ? tr::lng_comments_header_none + : tr::lng_replies_header_none)(); })) | rpl::flatten_latest( ) | rpl::start_with_next([=](const QString &text) { _topBar->setCustomTitle(text); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 76ba40c289..844175ab49 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -146,6 +146,8 @@ private: void setupComposeControls(); + void setupRoot(); + void refreshRootView(); void setupDragArea(); void setupScrollDownButton(); @@ -167,6 +169,8 @@ private: void chooseAttach(); [[nodiscard]] SendMenu::Type sendMenuType() const; [[nodiscard]] MsgId replyToId() const; + [[nodiscard]] HistoryItem *lookupRoot() const; + [[nodiscard]] bool computeAreComments() const; void pushReplyReturn(not_null item); void computeCurrentReplyReturn(); @@ -215,7 +219,9 @@ private: const not_null _history; const MsgId _rootId = 0; + HistoryItem *_root = nullptr; std::shared_ptr _replies; + rpl::variable _areComments = false; object_ptr _scroll; QPointer _inner; object_ptr _topBar; diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index d4117db3e9..c2e977e20f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -362,7 +362,7 @@ void Gif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms auto roundRadius = isRound ? ImageRoundRadius::Ellipse : inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; auto roundCorners = (isRound || inWebPage) ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) - | ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None)); + | ((isRoundedInBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None)); if (streamed) { auto paused = autoPaused; if (isRound) { @@ -1136,7 +1136,8 @@ bool Gif::needsBubble() const { return true; } const auto item = _parent->data(); - return item->viaBot() + return item->repliesAreComments() + || item->viaBot() || _parent->displayedReply() || _parent->displayForwardedFrom() || _parent->displayFromName(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index 31a32b94d5..e11bf0dbc0 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -99,7 +99,7 @@ public: QString additionalInfoString() const override; bool skipBubbleTail() const override { - return isBubbleBottom() && _caption.isEmpty(); + return isRoundedInBubbleBottom() && _caption.isEmpty(); } bool isReadyForOpen() const override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_location.cpp b/Telegram/SourceFiles/history/view/media/history_view_location.cpp index 212dee5b1d..4824dd6fcf 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_location.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_location.cpp @@ -184,7 +184,7 @@ void Location::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti auto roundRadius = ImageRoundRadius::Large; auto roundCorners = ((isBubbleTop() && _title.isEmpty() && _description.isEmpty()) ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) - | (isBubbleBottom() ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None); + | (isRoundedInBubbleBottom() ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None); auto rthumb = QRect(paintx, painty, paintw, painth); ensureMediaCreated(); if (const auto thumbnail = _media->image()) { @@ -319,11 +319,11 @@ bool Location::needsBubble() const { return true; } const auto item = _parent->data(); - return item->viaBot() + return item->repliesAreComments() + || item->viaBot() || _parent->displayedReply() || _parent->displayForwardedFrom() || _parent->displayFromName(); - return false; } int Location::fullWidth() const { diff --git a/Telegram/SourceFiles/history/view/media/history_view_location.h b/Telegram/SourceFiles/history/view/media/history_view_location.h index 1cfe606413..eceb8dfb6c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_location.h +++ b/Telegram/SourceFiles/history/view/media/history_view_location.h @@ -55,7 +55,7 @@ public: } bool skipBubbleTail() const override { - return isBubbleBottom(); + return isRoundedInBubbleBottom(); } void unloadHeavyPart() override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index f858fc7c07..30c26b9e0a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -186,4 +186,8 @@ TextState Media::getStateGrouped( Unexpected("Grouping method call."); } +bool Media::isRoundedInBubbleBottom() const { + return isBubbleBottom() && !_parent->data()->repliesAreComments(); +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index 76ac7bc26a..98be501182 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -217,6 +217,7 @@ public: return (_inBubbleState == MediaInBubbleState::Bottom) || (_inBubbleState == MediaInBubbleState::None); } + [[nodiscard]] bool isRoundedInBubbleBottom() const; [[nodiscard]] virtual bool skipBubbleTail() const { return false; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index d08bd4190e..ed3e0688a4 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -170,7 +170,7 @@ RectParts GroupedMedia::cornersFromSides(RectParts sides) const { if (!isBubbleTop()) { result &= ~(RectPart::TopLeft | RectPart::TopRight); } - if (!isBubbleBottom() || !_caption.isEmpty()) { + if (!isRoundedInBubbleBottom() || !_caption.isEmpty()) { result &= ~(RectPart::BottomLeft | RectPart::BottomRight); } return result; @@ -453,7 +453,8 @@ bool GroupedMedia::computeNeedBubble() const { return true; } if (const auto item = _parent->data()) { - if (item->viaBot() + if (item->repliesAreComments() + || item->viaBot() || _parent->displayedReply() || _parent->displayForwardedFrom() || _parent->displayFromName() diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h index 184278479e..92e7f33aa2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h @@ -76,7 +76,7 @@ public: HistoryMessageEdited *displayedEditBadge() const override; bool skipBubbleTail() const override { - return isBubbleBottom() && _caption.isEmpty(); + return isRoundedInBubbleBottom() && _caption.isEmpty(); } void updateNeedBubbleState() override; bool needsBubble() const override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index b06f27bc3f..d0c63e5833 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -250,7 +250,7 @@ void Photo::draw(Painter &p, const QRect &r, TextSelection selection, crl::time auto inWebPage = (_parent->media() != this); auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) - | ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None)); + | ((isRoundedInBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None)); const auto pix = [&] { if (const auto large = _dataMedia->image(PhotoSize::Large)) { return large->pixSingle(_pixw, _pixh, paintw, painth, roundRadius, roundCorners); @@ -801,7 +801,8 @@ bool Photo::needsBubble() const { } const auto item = _parent->data(); if (item->toHistoryMessage()) { - return item->viaBot() + return item->repliesAreComments() + || item->viaBot() || _parent->displayedReply() || _parent->displayForwardedFrom() || _parent->displayFromName(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index 5c97fd072b..b35880df02 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -83,7 +83,7 @@ public: return _caption.isEmpty(); } bool skipBubbleTail() const override { - return isBubbleBottom() && _caption.isEmpty(); + return isRoundedInBubbleBottom() && _caption.isEmpty(); } bool isReadyForOpen() const override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp index 2df30b38f1..bcc4c4ec32 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp @@ -1518,7 +1518,7 @@ void Poll::toggleLinkRipple(bool pressed) { radius); p.fillRect(0, 0, linkWidth, radius * 2, Qt::white); }; - auto mask = isBubbleBottom() + auto mask = isRoundedInBubbleBottom() ? Ui::RippleAnimation::maskByDrawer( QSize(linkWidth, linkHeight), false, diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index ad7ad4e374..dbf28f6529 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1306,13 +1306,7 @@ void MainWidget::viewsIncrementDone( item->setForwardsCount(forwards->v); } if (const auto replies = data.vreplies()) { - item->setRepliesCount( - replies->match([&](const MTPDmessageReplies &data) { - return data.vreplies().v; - }), - replies->match([&](const MTPDmessageReplies &data) { - return data.vreplies_pts().v; - })); + item->setReplies(*replies); } }); }