Add comments button to channel posts.

This commit is contained in:
John Preston 2020-09-03 11:19:02 +04:00
parent ce91caa820
commit 31e1ed216a
33 changed files with 642 additions and 139 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

View file

@ -1351,6 +1351,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_replies_header#other" = "{count} replies"; "lng_replies_header#other" = "{count} replies";
"lng_replies_header_none" = "No replies"; "lng_replies_header_none" = "No replies";
"lng_replies_send_placeholder" = "Reply"; "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_name" = "Archived chats";
"lng_archived_add" = "Archive"; "lng_archived_add" = "Archive";

View file

@ -145,6 +145,9 @@ const ChannelLocation *ChannelData::getLocation() const {
void ChannelData::setLinkedChat(ChannelData *linked) { void ChannelData::setLinkedChat(ChannelData *linked) {
if (_linkedChat != linked) { if (_linkedChat != linked) {
_linkedChat = linked; _linkedChat = linked;
if (const auto history = owner().historyLoaded(this)) {
history->forceFullResize();
}
session().changes().peerUpdated(this, UpdateFlag::ChannelLinkedChat); session().changes().peerUpdated(this, UpdateFlag::ChannelLinkedChat);
} }
} }

View file

@ -1663,11 +1663,7 @@ bool Session::checkEntitiesAndViewsUpdate(const MTPDmessage &data) {
existing->updateForwardedInfo(data.vfwd_from()); existing->updateForwardedInfo(data.vfwd_from());
existing->setViewsCount(data.vviews().value_or(-1)); existing->setViewsCount(data.vviews().value_or(-1));
if (const auto replies = data.vreplies()) { if (const auto replies = data.vreplies()) {
replies->match([&](const MTPDmessageReplies &data) { existing->setReplies(*replies);
existing->setRepliesCount(
data.vreplies().v,
data.vreplies_pts().v);
});
} }
existing->setForwardsCount(data.vforwards().value_or(-1)); existing->setForwardsCount(data.vforwards().value_or(-1));
if (const auto reply = data.vreply_to()) { if (const auto reply = data.vreply_to()) {

View file

@ -615,6 +615,14 @@ historyPollOutChosenSelected: icon {{ "poll_select_check", historyFileOutIconFgS
historyPollInChosen: icon {{ "poll_select_check", historyFileInIconFg }}; historyPollInChosen: icon {{ "poll_select_check", historyFileInIconFg }};
historyPollInChosenSelected: icon {{ "poll_select_check", historyFileInIconFgSelected }}; historyPollInChosenSelected: icon {{ "poll_select_check", historyFileInIconFgSelected }};
historyCommentsButtonHeight: 40px;
historyCommentsSkipLeft: 9px;
historyCommentsSkipText: 10px;
historyCommentsUserpicSize: 25px;
historyCommentsUserpicStroke: 2px;
historyCommentsUserpicOverlap: 6px;
historyCommentsSkipRight: 8px;
boxAttachEmoji: IconButton(historyAttachEmoji) { boxAttachEmoji: IconButton(historyAttachEmoji) {
width: 30px; width: 30px;
height: 30px; height: 30px;
@ -658,6 +666,15 @@ historyQuizTimerInSelected: icon {{ "quiz_timer", msgFileThumbLinkInFgSelected }
historyQuizTimerOut: icon {{ "quiz_timer", msgFileThumbLinkOutFg }}; historyQuizTimerOut: icon {{ "quiz_timer", msgFileThumbLinkOutFg }};
historyQuizTimerOutSelected: icon {{ "quiz_timer", msgFileThumbLinkOutFgSelected }}; 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); historySlowmodeCounterMargins: margins(0px, 0px, 10px, 0px);
largeEmojiSize: 36px; largeEmojiSize: 36px;

View file

@ -1542,12 +1542,18 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}); });
} }
if (IsServerMsgId(item->id) && item->repliesCount() > 0) { 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( controller->showSection(
HistoryView::RepliesMemento(_history, itemId.msg)); HistoryView::RepliesMemento(_history, itemId.msg));
}); });
} else if (const auto replyToTop = item->replyToTop()) { } 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( controller->showSection(
HistoryView::RepliesMemento(_history, replyToTop)); HistoryView::RepliesMemento(_history, replyToTop));
}); });

View file

@ -195,6 +195,9 @@ public:
[[nodiscard]] virtual int repliesCount() const { [[nodiscard]] virtual int repliesCount() const {
return 0; return 0;
} }
[[nodiscard]] virtual bool repliesAreComments() const {
return false;
}
[[nodiscard]] virtual bool needCheck() const; [[nodiscard]] virtual bool needCheck() const;
@ -252,7 +255,9 @@ public:
} }
virtual void setForwardsCount(int count) { 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) { virtual void setReplyToTop(MsgId replyToTop) {
} }

View file

@ -34,10 +34,16 @@ struct HistoryMessageVia : public RuntimeComponent<HistoryMessageVia, HistoryIte
}; };
struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews, HistoryItem> { struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews, HistoryItem> {
QString text; struct Part {
int textWidth = 0; QString text;
int views = -1; int textWidth = 0;
int replies = 0; int count = -1;
};
std::vector<UserId> recentRepliers;
Part views;
Part replies;
ChannelId repliesChannelId = 0;
static constexpr auto kMaxRecentRepliers = 3;
}; };
struct HistoryMessageSigned : public RuntimeComponent<HistoryMessageSigned, HistoryItem> { struct HistoryMessageSigned : public RuntimeComponent<HistoryMessageSigned, HistoryItem> {

View file

@ -409,7 +409,6 @@ struct HistoryMessage::CreateConfig {
MsgId replyToTop = 0; MsgId replyToTop = 0;
UserId viaBotId = 0; UserId viaBotId = 0;
int viewsCount = -1; int viewsCount = -1;
int repliesCount = 0;
QString author; QString author;
PeerId senderOriginal = 0; PeerId senderOriginal = 0;
QString senderNameOriginal; QString senderNameOriginal;
@ -422,6 +421,7 @@ struct HistoryMessage::CreateConfig {
TimeId editDate = 0; TimeId editDate = 0;
// For messages created from MTP structs. // For messages created from MTP structs.
const MTPMessageReplies *mtpReplies = nullptr;
const MTPReplyMarkup *mtpMarkup = nullptr; const MTPReplyMarkup *mtpMarkup = nullptr;
// For messages created from existing messages (forwarded). // For messages created from existing messages (forwarded).
@ -474,11 +474,7 @@ HistoryMessage::HistoryMessage(
} }
config.viaBotId = data.vvia_bot_id().value_or_empty(); config.viaBotId = data.vvia_bot_id().value_or_empty();
config.viewsCount = data.vviews().value_or(-1); config.viewsCount = data.vviews().value_or(-1);
if (const auto replies = data.vreplies()) { config.mtpReplies = data.vreplies();
replies->match([&](const MTPDmessageReplies &data) {
config.repliesCount = data.vreplies().v;
});
}
config.mtpMarkup = data.vreply_markup(); config.mtpMarkup = data.vreply_markup();
config.editDate = data.vedit_date().value_or_empty(); config.editDate = data.vedit_date().value_or_empty();
config.author = qs(data.vpost_author().value_or_empty()); config.author = qs(data.vpost_author().value_or_empty());
@ -744,18 +740,45 @@ void HistoryMessage::createComponentsHelper(
int HistoryMessage::viewsCount() const { int HistoryMessage::viewsCount() const {
if (const auto views = Get<HistoryMessageViews>()) { if (const auto views = Get<HistoryMessageViews>()) {
return views->views; return std::max(views->views.count, 0);
} }
return HistoryItem::viewsCount(); return HistoryItem::viewsCount();
} }
int HistoryMessage::repliesCount() const { int HistoryMessage::repliesCount() const {
if (const auto views = Get<HistoryMessageViews>()) { if (const auto views = Get<HistoryMessageViews>()) {
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(); return HistoryItem::repliesCount();
} }
bool HistoryMessage::repliesAreComments() const {
if (const auto views = Get<HistoryMessageViews>()) {
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() { bool HistoryMessage::updateDependencyItem() {
if (const auto reply = Get<HistoryMessageReply>()) { if (const auto reply = Get<HistoryMessageReply>()) {
const auto documentId = reply->replyToDocumentId; const auto documentId = reply->replyToDocumentId;
@ -848,7 +871,7 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
if (config.viaBotId) { if (config.viaBotId) {
mask |= HistoryMessageVia::Bit(); mask |= HistoryMessageVia::Bit();
} }
if (config.viewsCount >= 0 || config.repliesCount > 0) { if (config.viewsCount >= 0 || config.mtpReplies) {
mask |= HistoryMessageViews::Bit(); mask |= HistoryMessageViews::Bit();
} }
if (!config.author.isEmpty()) { if (!config.author.isEmpty()) {
@ -886,8 +909,10 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
via->create(&history()->owner(), config.viaBotId); via->create(&history()->owner(), config.viaBotId);
} }
if (const auto views = Get<HistoryMessageViews>()) { if (const auto views = Get<HistoryMessageViews>()) {
views->views = config.viewsCount; setViewsCount(config.viewsCount);
views->replies = config.repliesCount; if (config.mtpReplies) {
setReplies(*config.mtpReplies);
}
} }
if (const auto edited = Get<HistoryMessageEdited>()) { if (const auto edited = Get<HistoryMessageEdited>()) {
edited->date = config.editDate; edited->date = config.editDate;
@ -1400,20 +1425,20 @@ bool HistoryMessage::textHasLinks() const {
void HistoryMessage::setViewsCount(int count) { void HistoryMessage::setViewsCount(int count) {
const auto views = Get<HistoryMessageViews>(); const auto views = Get<HistoryMessageViews>();
if (!views if (!views
|| views->views == count || views->views.count == count
|| (count >= 0 && views->views > count)) { || (count >= 0 && views->views.count > count)) {
return; return;
} }
views->views = count; views->views.count = count;
views->text = (views->views > 0) views->views.text = Lang::FormatCountToShort(
? Lang::FormatCountToShort(views->views).string std::max(views->views.count, 1)
: QString("1"); ).string;
const auto was = views->textWidth; const auto was = views->views.textWidth;
views->textWidth = views->text.isEmpty() views->views.textWidth = views->views.text.isEmpty()
? 0 ? 0
: st::msgDateFont->width(views->text); : st::msgDateFont->width(views->views.text);
if (was == views->textWidth) { if (was == views->views.textWidth) {
history()->owner().requestItemRepaint(this); history()->owner().requestItemRepaint(this);
} else { } else {
history()->owner().requestItemResize(this); history()->owner().requestItemResize(this);
@ -1423,39 +1448,88 @@ void HistoryMessage::setViewsCount(int count) {
void HistoryMessage::setForwardsCount(int count) { void HistoryMessage::setForwardsCount(int count) {
} }
void HistoryMessage::setRepliesCount(int count, int pts) { void HistoryMessage::setReplies(const MTPMessageReplies &data) {
auto views = Get<HistoryMessageViews>(); data.match([&](const MTPDmessageReplies &data) {
if (!views) { auto views = Get<HistoryMessageViews>();
if (!count) { if (!views) {
AddComponents(HistoryMessageViews::Bit());
views = Get<HistoryMessageViews>();
}
const auto repliers = [&] {
auto result = std::vector<UserId>();
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; return;
} }
AddComponents(HistoryMessageViews::Bit()); views->replies.count = count;
views = Get<HistoryMessageViews>(); if (recentChanged) {
} views->recentRepliers = repliers;
if (views->replies == count) { }
return; views->repliesChannelId = channelId;
} refreshRepliesText(views, channelChanged);
views->replies = count; });
if (views->views >= 0) { }
return;
} else if (!views->replies) {
RemoveComponents(HistoryMessageViews::Bit());
history()->owner().requestItemResize(this);
return;
}
views->text = (views->replies > 0) void HistoryMessage::refreshRepliesText(
? Lang::FormatCountToShort(views->replies).string not_null<HistoryMessageViews*> views,
: QString(); bool forceResize) {
const auto was = views->textWidth; const auto was = views->replies.textWidth;
views->textWidth = views->text.isEmpty() if (views->repliesChannelId) {
? 0 views->replies.text = (views->replies.count > 0)
: st::msgDateFont->width(views->text); ? tr::lng_comments_open_count(
if (was == views->textWidth) { tr::now,
history()->owner().requestItemRepaint(this); lt_count_short,
views->replies.count)
: tr::lng_comments_open_none(tr::now);
views->replies.textWidth = st::semiboldFont->width(
views->replies.text);
} else { } 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<HistoryMessageViews>();
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) { void HistoryMessage::setReplyToTop(MsgId replyToTop) {
@ -1499,7 +1573,13 @@ void HistoryMessage::incrementReplyToTopCounter(
channelId, channelId,
reply->replyToTop()); reply->replyToTop());
if (top) { 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( const auto top = history()->owner().message(
channelId, channelId,
reply->replyToTop()); reply->replyToTop());
if (const auto replies = (top ? top->repliesCount() : 0)) { if (top) {
top->setRepliesCount(replies - 1, 0); if (const auto from = displayFrom()) {
if (const auto user = from->asUser()) {
top->changeRepliesCount(-1, user->bareId());
return;
}
}
top->changeRepliesCount(-1, UserId());
} }
} }
} }

View file

@ -19,6 +19,7 @@ class Message;
struct HistoryMessageEdited; struct HistoryMessageEdited;
struct HistoryMessageReply; struct HistoryMessageReply;
struct HistoryMessageViews;
Fn<void(ChannelData*, MsgId)> HistoryDependentItemCallback( Fn<void(ChannelData*, MsgId)> HistoryDependentItemCallback(
not_null<HistoryItem*> item); not_null<HistoryItem*> item);
@ -133,7 +134,8 @@ public:
void setViewsCount(int count) override; void setViewsCount(int count) override;
void setForwardsCount(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 setReplyToTop(MsgId replyToTop) override;
void setRealId(MsgId newId) override; void setRealId(MsgId newId) override;
void incrementReplyToTopCounter() override; void incrementReplyToTopCounter() override;
@ -165,6 +167,7 @@ public:
[[nodiscard]] int viewsCount() const override; [[nodiscard]] int viewsCount() const override;
[[nodiscard]] int repliesCount() const override; [[nodiscard]] int repliesCount() const override;
[[nodiscard]] bool repliesAreComments() const override;
bool updateDependencyItem() override; bool updateDependencyItem() override;
[[nodiscard]] MsgId dependencyMsgId() const override { [[nodiscard]] MsgId dependencyMsgId() const override {
return replyToId(); return replyToId();
@ -206,6 +209,9 @@ private:
void setupForwardedComponent(const CreateConfig &config); void setupForwardedComponent(const CreateConfig &config);
void incrementReplyToTopCounter(not_null<HistoryMessageReply*> reply); void incrementReplyToTopCounter(not_null<HistoryMessageReply*> reply);
void decrementReplyToTopCounter(not_null<HistoryMessageReply*> reply); void decrementReplyToTopCounter(not_null<HistoryMessageReply*> reply);
void refreshRepliesText(
not_null<HistoryMessageViews*> views,
bool forceResize = false);
static void FillForwardedInfo( static void FillForwardedInfo(
CreateConfig &config, CreateConfig &config,

View file

@ -624,8 +624,12 @@ auto Element::verticalRepaintRange() const -> VerticalRepaintRange {
}; };
} }
bool Element::hasHeavyPart() const {
return false;
}
void Element::checkHeavyPart() { void Element::checkHeavyPart() {
if (!_media || !_media->hasHeavyPart()) { if (!hasHeavyPart() && (!_media || !_media->hasHeavyPart())) {
history()->owner().unregisterHeavyViewPart(this); history()->owner().unregisterHeavyViewPart(this);
} }
} }

View file

@ -278,8 +278,9 @@ public:
}; };
[[nodiscard]] virtual VerticalRepaintRange verticalRepaintRange() const; [[nodiscard]] virtual VerticalRepaintRange verticalRepaintRange() const;
virtual bool hasHeavyPart() const;
virtual void unloadHeavyPart();
void checkHeavyPart(); void checkHeavyPart();
void unloadHeavyPart();
// Legacy blocks structure. // Legacy blocks structure.
HistoryBlock *block(); HistoryBlock *block();

View file

@ -12,7 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_message.h" #include "history/history_message.h"
#include "history/view/media/history_view_media.h" #include "history/view/media/history_view_media.h"
#include "history/view/media/history_view_web_page.h" #include "history/view/media/history_view_web_page.h"
#include "history/view/history_view_replies_section.h"
#include "history/history.h" #include "history/history.h"
#include "ui/effects/ripple_animation.h"
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
@ -189,6 +191,19 @@ style::color FromNameFg(PeerId peerId, bool selected) {
} // namespace } // namespace
struct Message::CommentsButton {
struct Userpic {
not_null<UserData*> user;
std::shared_ptr<Data::CloudImageView> view;
InMemoryKey uniqueKey;
};
std::unique_ptr<Ui::RippleAnimation> ripple;
std::vector<Userpic> userpics;
QImage cachedUserpics;
ClickHandlerPtr link;
QPoint lastPoint;
};
LogEntryOriginal::LogEntryOriginal() = default; LogEntryOriginal::LogEntryOriginal() = default;
LogEntryOriginal::LogEntryOriginal(LogEntryOriginal &&other) LogEntryOriginal::LogEntryOriginal(LogEntryOriginal &&other)
@ -211,6 +226,13 @@ Message::Message(
initPsa(); initPsa();
} }
Message::~Message() {
if (_comments) {
_comments = nullptr;
checkHeavyPart();
}
}
not_null<HistoryMessage*> Message::message() const { not_null<HistoryMessage*> Message::message() const {
return static_cast<HistoryMessage*>(data().get()); return static_cast<HistoryMessage*>(data().get());
} }
@ -460,6 +482,9 @@ void Message::draw(
auto displayTail = skipTail ? RectPart::None : (outbg && !Core::App().settings().chatWide()) ? RectPart::Right : RectPart::Left; auto displayTail = skipTail ? RectPart::None : (outbg && !Core::App().settings().chatWide()) ? RectPart::Right : RectPart::Left;
PaintBubble(p, g, width(), selected, outbg, displayTail); PaintBubble(p, g, width(), selected, outbg, displayTail);
const auto gBubble = g;
paintCommentsButton(p, g, selected);
// Entry page is always a bubble bottom. // Entry page is always a bubble bottom.
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
@ -507,19 +532,28 @@ void Message::draw(
: true); : true);
if (needDrawInfo) { if (needDrawInfo) {
drawInfo(p, g.left() + g.width(), g.top() + g.height(), 2 * g.left() + g.width(), selected, InfoDisplayType::Default); 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()) { if (displayRightAction()) {
const auto fastShareSkip = snap( const auto fastShareSkip = std::clamp(
(g.height() - st::historyFastShareSize) / 2, (gBubble.height() - st::historyFastShareSize) / 2,
0, 0,
st::historyFastShareBottom); st::historyFastShareBottom);
const auto fastShareLeft = g.left() + g.width() + st::historyFastShareLeft; 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()); drawRightAction(p, fastShareLeft, fastShareTop, width());
} }
if (media) { if (media) {
media->paintBubbleFireworks(p, g, ms); media->paintBubbleFireworks(p, gBubble, ms);
} }
} else if (media && media->isDisplayed()) { } else if (media && media->isDisplayed()) {
p.translate(g.topLeft()); 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<CommentsButton>();
history()->owner().registerHeavyViewPart(const_cast<Message*>(this));
}
const auto outbg = hasOutLayout();
const auto views = data()->Get<HistoryMessageViews>();
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( void Message::paintFromName(
Painter &p, Painter &p,
QRect &trect, QRect &trect,
@ -733,7 +898,7 @@ void Message::paintText(Painter &p, QRect &trect, TextSelection selection) const
} }
PointState Message::pointState(QPoint point) const { PointState Message::pointState(QPoint point) const {
const auto g = countGeometry(); auto g = countGeometry();
if (g.width() < 1 || isHidden()) { if (g.width() < 1 || isHidden()) {
return PointState::Outside; return PointState::Outside;
} }
@ -752,6 +917,10 @@ PointState Message::pointState(QPoint point) const {
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
if (item->repliesAreComments()) {
g.setHeight(g.height() - st::historyCommentsButtonHeight);
}
auto trect = g.marginsRemoved(st::msgPadding); auto trect = g.marginsRemoved(st::msgPadding);
if (mediaOnBottom) { if (mediaOnBottom) {
trect.setHeight(trect.height() + st::msgPadding.bottom()); trect.setHeight(trect.height() + st::msgPadding.bottom());
@ -788,6 +957,65 @@ bool Message::displayFromPhoto() const {
return hasFromPhoto() && !isAttachedToNext(); 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<Ui::RippleAnimation>(
(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 { bool Message::hasFromPhoto() const {
if (isHidden()) { if (isHidden()) {
return false; return false;
@ -842,6 +1070,11 @@ TextState Message::textState(
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); 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); auto trect = g.marginsRemoved(st::msgPadding);
if (mediaOnBottom) { if (mediaOnBottom) {
trect.setHeight(trect.height() + st::msgPadding.bottom()); trect.setHeight(trect.height() + st::msgPadding.bottom());
@ -913,11 +1146,11 @@ TextState Message::textState(
checkForPointInTime(); checkForPointInTime();
if (displayRightAction()) { if (displayRightAction()) {
const auto fastShareSkip = snap( const auto fastShareSkip = snap(
(g.height() - st::historyFastShareSize) / 2, (gBubble.height() - st::historyFastShareSize) / 2,
0, 0,
st::historyFastShareBottom); st::historyFastShareBottom);
const auto fastShareLeft = g.left() + g.width() + st::historyFastShareLeft; 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( if (QRect(
fastShareLeft, fastShareLeft,
fastShareTop, fastShareTop,
@ -943,6 +1176,34 @@ TextState Message::textState(
return result; return result;
} }
bool Message::getStateCommentsButton(
QPoint point,
QRect &g,
not_null<TextState*> 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<LambdaClickHandler>([=] {
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( bool Message::getStateFromName(
QPoint point, QPoint point,
QRect &trect, QRect &trect,
@ -1344,32 +1605,67 @@ void Message::drawInfo(
} }
if (auto views = item->Get<HistoryMessageViews>()) { if (auto views = item->Get<HistoryMessageViews>()) {
const auto showReplies = /*(views->views < 0) && */(views->replies > 0); auto left = infoRight - infoW;
auto icon = [&] { const auto iconTop = infoBottom + st::historyViewsTop;
if (item->id > 0) { const auto textTop = infoBottom - st::msgDateFont->descent;
if (outbg) { 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 return &(invertedsprites
? (showReplies ? st::historyRepliesInvertedIcon : st::historyViewsInvertedIcon) ? st::historyRepliesInvertedIcon
: selected : selected
? (showReplies ? st::historyRepliesOutSelectedIcon : st::historyViewsOutSelectedIcon) ? st::historyRepliesInSelectedIcon
: (showReplies ? st::historyRepliesOutIcon : st::historyViewsOutIcon)); : st::historyRepliesInIcon);
} }
return &(invertedsprites return &(invertedsprites
? (showReplies ? st::historyRepliesInvertedIcon : st::historyViewsInvertedIcon) ? st::historyViewsSendingInvertedIcon
: selected : st::historyViewsSendingIcon);
? (showReplies ? st::historyRepliesInSelectedIcon : st::historyViewsInSelectedIcon) }();
: (showReplies ? st::historyRepliesInIcon : st::historyViewsInIcon)); 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) { } else if (item->id < 0 && item->history()->peer->isSelf() && !outbg) {
auto icon = &(invertedsprites ? st::historyViewsSendingInvertedIcon : st::historyViewsSendingIcon); auto icon = &(invertedsprites ? st::historyViewsSendingInvertedIcon : st::historyViewsSendingIcon);
@ -1424,9 +1720,16 @@ int Message::infoWidth() const {
const auto item = message(); const auto item = message();
auto result = item->_timeWidth; auto result = item->_timeWidth;
if (auto views = item->Get<HistoryMessageViews>()) { if (auto views = item->Get<HistoryMessageViews>()) {
result += st::historyViewsSpace if (views->views.count >= 0) {
+ views->textWidth result += st::historyViewsSpace
+ st::historyViewsWidth; + 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()) { } else if (item->id < 0 && item->history()->peer->isSelf()) {
if (!hasOutLayout()) { if (!hasOutLayout()) {
result += st::historySendStateSpace; result += st::historySendStateSpace;
@ -1465,7 +1768,12 @@ int Message::timeLeft() const {
const auto item = message(); const auto item = message();
auto result = 0; auto result = 0;
if (auto views = item->Get<HistoryMessageViews>()) { if (auto views = item->Get<HistoryMessageViews>()) {
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()) { } else if (item->id < 0 && item->history()->peer->isSelf()) {
if (!hasOutLayout()) { if (!hasOutLayout()) {
result += st::historySendStateSpace; result += st::historySendStateSpace;
@ -1950,6 +2258,10 @@ int Message::resizeContentGetHeight(int newWidth) {
reply->resize(contentWidth - st::msgPadding.left() - st::msgPadding.right()); reply->resize(contentWidth - st::msgPadding.left() - st::msgPadding.right());
newHeight += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); newHeight += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
} }
if (item->repliesAreComments()) {
newHeight += st::historyCommentsButtonHeight;
}
} else if (mediaDisplayed) { } else if (mediaDisplayed) {
newHeight = media->height(); newHeight = media->height();
} else { } else {
@ -2007,18 +2319,6 @@ void Message::initTime() {
item->_timeText = dateTime().toString(cTimeFormat()); item->_timeText = dateTime().toString(cTimeFormat());
item->_timeWidth = st::msgDateFont->width(item->_timeText); item->_timeWidth = st::msgDateFont->width(item->_timeText);
} }
if (const auto views = item->Get<HistoryMessageViews>()) {
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.hasSkipBlock()) {
if (item->_text.updateSkipBlock(skipBlockWidth(), skipBlockHeight())) { if (item->_text.updateSkipBlock(skipBlockWidth(), skipBlockHeight())) {
item->_textWidth = -1; item->_textWidth = -1;

View file

@ -43,6 +43,11 @@ public:
not_null<ElementDelegate*> delegate, not_null<ElementDelegate*> delegate,
not_null<HistoryMessage*> data, not_null<HistoryMessage*> data,
Element *replacing); Element *replacing);
~Message();
void clickHandlerPressedChanged(
const ClickHandlerPtr &handler,
bool pressed) override;
int marginTop() const override; int marginTop() const override;
int marginBottom() const override; int marginBottom() const override;
@ -73,6 +78,9 @@ public:
TextSelection selection, TextSelection selection,
TextSelectType type) const override; TextSelectType type) const override;
bool hasHeavyPart() const override;
void unloadHeavyPart() override;
// hasFromPhoto() returns true even if we don't display the photo // 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 // but we need to skip a place at the left side for this photo
bool hasFromPhoto() const override; bool hasFromPhoto() const override;
@ -103,6 +111,8 @@ protected:
void refreshDataIdHook() override; void refreshDataIdHook() override;
private: private:
struct CommentsButton;
not_null<HistoryMessage*> message() const; not_null<HistoryMessage*> message() const;
void initLogEntryOriginal(); void initLogEntryOriginal();
@ -115,6 +125,9 @@ private:
[[nodiscard]] TextSelection unskipTextSelection( [[nodiscard]] TextSelection unskipTextSelection(
TextSelection selection) const; 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 paintFromName(Painter &p, QRect &trect, bool selected) const;
void paintForwardedInfo(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; void paintReplyInfo(Painter &p, QRect &trect, bool selected) const;
@ -122,6 +135,10 @@ private:
void paintViaBotIdInfo(Painter &p, QRect &trect, bool selected) const; void paintViaBotIdInfo(Painter &p, QRect &trect, bool selected) const;
void paintText(Painter &p, QRect &trect, TextSelection selection) const; void paintText(Painter &p, QRect &trect, TextSelection selection) const;
bool getStateCommentsButton(
QPoint point,
QRect &g,
not_null<TextState*> outResult) const;
bool getStateFromName( bool getStateFromName(
QPoint point, QPoint point,
QRect &trect, QRect &trect,
@ -170,6 +187,7 @@ private:
mutable ClickHandlerPtr _rightActionLink; mutable ClickHandlerPtr _rightActionLink;
mutable ClickHandlerPtr _fastReplyLink; mutable ClickHandlerPtr _fastReplyLink;
mutable std::unique_ptr<CommentsButton> _comments;
int _bubbleWidthLimit = 0; int _bubbleWidthLimit = 0;
}; };

View file

@ -105,6 +105,8 @@ RepliesWidget::RepliesWidget(
: Window::SectionWidget(parent, controller) : Window::SectionWidget(parent, controller)
, _history(history) , _history(history)
, _rootId(rootId) , _rootId(rootId)
, _root(lookupRoot())
, _areComments(computeAreComments())
, _scroll(this, st::historyScroll, false) , _scroll(this, st::historyScroll, false)
, _topBar(this, controller) , _topBar(this, controller)
, _topBarShadow(this) , _topBarShadow(this)
@ -113,6 +115,8 @@ RepliesWidget::RepliesWidget(
controller, controller,
ComposeControls::Mode::Normal)) ComposeControls::Mode::Normal))
, _scrollDown(_scroll, st::historyToDown) { , _scrollDown(_scroll, st::historyToDown) {
setupRoot();
_topBar->setActiveChat(_history, TopBarWidget::Section::Replies); _topBar->setActiveChat(_history, TopBarWidget::Section::Replies);
_topBar->move(0, 0); _topBar->move(0, 0);
@ -179,6 +183,33 @@ RepliesWidget::RepliesWidget(
RepliesWidget::~RepliesWidget() = default; 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() { void RepliesWidget::setupComposeControls() {
_composeControls->setHistory(_history); _composeControls->setHistory(_history);
@ -246,7 +277,7 @@ void RepliesWidget::setupComposeControls() {
) | rpl::start_with_next([=](not_null<QKeyEvent*> e) { ) | rpl::start_with_next([=](not_null<QKeyEvent*> e) {
if (e->key() == Qt::Key_Up) { if (e->key() == Qt::Key_Up) {
if (!_composeControls->isEditingMessage()) { if (!_composeControls->isEditingMessage()) {
// #TODO replies // #TODO replies edit last sent message
//auto &messages = session().data().scheduledMessages(); //auto &messages = session().data().scheduledMessages();
//if (const auto item = messages.lastSentMessage(_history)) { //if (const auto item = messages.lastSentMessage(_history)) {
// _inner->editMessageRequestNotify(item->fullId()); // _inner->editMessageRequestNotify(item->fullId());
@ -1064,13 +1095,19 @@ void RepliesWidget::restoreState(not_null<RepliesMemento*> memento) {
rpl::single( rpl::single(
tr::lng_contacts_loading() tr::lng_contacts_loading()
) | rpl::then(_replies->fullCount( ) | rpl::then(rpl::combine(
) | rpl::map([=](int count) { _replies->fullCount(),
_areComments.value()
) | rpl::map([=](int count, bool areComments) {
return count return count
? tr::lng_replies_header( ? (areComments
lt_count, ? tr::lng_comments_header
rpl::single(count) | tr::to_count()) : tr::lng_replies_header)(
: tr::lng_replies_header_none(); lt_count,
rpl::single(count) | tr::to_count())
: (areComments
? tr::lng_comments_header_none
: tr::lng_replies_header_none)();
})) | rpl::flatten_latest( })) | rpl::flatten_latest(
) | rpl::start_with_next([=](const QString &text) { ) | rpl::start_with_next([=](const QString &text) {
_topBar->setCustomTitle(text); _topBar->setCustomTitle(text);

View file

@ -146,6 +146,8 @@ private:
void setupComposeControls(); void setupComposeControls();
void setupRoot();
void refreshRootView();
void setupDragArea(); void setupDragArea();
void setupScrollDownButton(); void setupScrollDownButton();
@ -167,6 +169,8 @@ private:
void chooseAttach(); void chooseAttach();
[[nodiscard]] SendMenu::Type sendMenuType() const; [[nodiscard]] SendMenu::Type sendMenuType() const;
[[nodiscard]] MsgId replyToId() const; [[nodiscard]] MsgId replyToId() const;
[[nodiscard]] HistoryItem *lookupRoot() const;
[[nodiscard]] bool computeAreComments() const;
void pushReplyReturn(not_null<HistoryItem*> item); void pushReplyReturn(not_null<HistoryItem*> item);
void computeCurrentReplyReturn(); void computeCurrentReplyReturn();
@ -215,7 +219,9 @@ private:
const not_null<History*> _history; const not_null<History*> _history;
const MsgId _rootId = 0; const MsgId _rootId = 0;
HistoryItem *_root = nullptr;
std::shared_ptr<Data::RepliesList> _replies; std::shared_ptr<Data::RepliesList> _replies;
rpl::variable<bool> _areComments = false;
object_ptr<Ui::ScrollArea> _scroll; object_ptr<Ui::ScrollArea> _scroll;
QPointer<ListWidget> _inner; QPointer<ListWidget> _inner;
object_ptr<TopBarWidget> _topBar; object_ptr<TopBarWidget> _topBar;

View file

@ -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 roundRadius = isRound ? ImageRoundRadius::Ellipse : inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
auto roundCorners = (isRound || inWebPage) ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) 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) { if (streamed) {
auto paused = autoPaused; auto paused = autoPaused;
if (isRound) { if (isRound) {
@ -1136,7 +1136,8 @@ bool Gif::needsBubble() const {
return true; return true;
} }
const auto item = _parent->data(); const auto item = _parent->data();
return item->viaBot() return item->repliesAreComments()
|| item->viaBot()
|| _parent->displayedReply() || _parent->displayedReply()
|| _parent->displayForwardedFrom() || _parent->displayForwardedFrom()
|| _parent->displayFromName(); || _parent->displayFromName();

View file

@ -99,7 +99,7 @@ public:
QString additionalInfoString() const override; QString additionalInfoString() const override;
bool skipBubbleTail() const override { bool skipBubbleTail() const override {
return isBubbleBottom() && _caption.isEmpty(); return isRoundedInBubbleBottom() && _caption.isEmpty();
} }
bool isReadyForOpen() const override; bool isReadyForOpen() const override;

View file

@ -184,7 +184,7 @@ void Location::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti
auto roundRadius = ImageRoundRadius::Large; auto roundRadius = ImageRoundRadius::Large;
auto roundCorners = ((isBubbleTop() && _title.isEmpty() && _description.isEmpty()) ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) 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); auto rthumb = QRect(paintx, painty, paintw, painth);
ensureMediaCreated(); ensureMediaCreated();
if (const auto thumbnail = _media->image()) { if (const auto thumbnail = _media->image()) {
@ -319,11 +319,11 @@ bool Location::needsBubble() const {
return true; return true;
} }
const auto item = _parent->data(); const auto item = _parent->data();
return item->viaBot() return item->repliesAreComments()
|| item->viaBot()
|| _parent->displayedReply() || _parent->displayedReply()
|| _parent->displayForwardedFrom() || _parent->displayForwardedFrom()
|| _parent->displayFromName(); || _parent->displayFromName();
return false;
} }
int Location::fullWidth() const { int Location::fullWidth() const {

View file

@ -55,7 +55,7 @@ public:
} }
bool skipBubbleTail() const override { bool skipBubbleTail() const override {
return isBubbleBottom(); return isRoundedInBubbleBottom();
} }
void unloadHeavyPart() override; void unloadHeavyPart() override;

View file

@ -186,4 +186,8 @@ TextState Media::getStateGrouped(
Unexpected("Grouping method call."); Unexpected("Grouping method call.");
} }
bool Media::isRoundedInBubbleBottom() const {
return isBubbleBottom() && !_parent->data()->repliesAreComments();
}
} // namespace HistoryView } // namespace HistoryView

View file

@ -217,6 +217,7 @@ public:
return (_inBubbleState == MediaInBubbleState::Bottom) return (_inBubbleState == MediaInBubbleState::Bottom)
|| (_inBubbleState == MediaInBubbleState::None); || (_inBubbleState == MediaInBubbleState::None);
} }
[[nodiscard]] bool isRoundedInBubbleBottom() const;
[[nodiscard]] virtual bool skipBubbleTail() const { [[nodiscard]] virtual bool skipBubbleTail() const {
return false; return false;
} }

View file

@ -170,7 +170,7 @@ RectParts GroupedMedia::cornersFromSides(RectParts sides) const {
if (!isBubbleTop()) { if (!isBubbleTop()) {
result &= ~(RectPart::TopLeft | RectPart::TopRight); result &= ~(RectPart::TopLeft | RectPart::TopRight);
} }
if (!isBubbleBottom() || !_caption.isEmpty()) { if (!isRoundedInBubbleBottom() || !_caption.isEmpty()) {
result &= ~(RectPart::BottomLeft | RectPart::BottomRight); result &= ~(RectPart::BottomLeft | RectPart::BottomRight);
} }
return result; return result;
@ -453,7 +453,8 @@ bool GroupedMedia::computeNeedBubble() const {
return true; return true;
} }
if (const auto item = _parent->data()) { if (const auto item = _parent->data()) {
if (item->viaBot() if (item->repliesAreComments()
|| item->viaBot()
|| _parent->displayedReply() || _parent->displayedReply()
|| _parent->displayForwardedFrom() || _parent->displayForwardedFrom()
|| _parent->displayFromName() || _parent->displayFromName()

View file

@ -76,7 +76,7 @@ public:
HistoryMessageEdited *displayedEditBadge() const override; HistoryMessageEdited *displayedEditBadge() const override;
bool skipBubbleTail() const override { bool skipBubbleTail() const override {
return isBubbleBottom() && _caption.isEmpty(); return isRoundedInBubbleBottom() && _caption.isEmpty();
} }
void updateNeedBubbleState() override; void updateNeedBubbleState() override;
bool needsBubble() const override; bool needsBubble() const override;

View file

@ -250,7 +250,7 @@ void Photo::draw(Painter &p, const QRect &r, TextSelection selection, crl::time
auto inWebPage = (_parent->media() != this); auto inWebPage = (_parent->media() != this);
auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) 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 = [&] { const auto pix = [&] {
if (const auto large = _dataMedia->image(PhotoSize::Large)) { if (const auto large = _dataMedia->image(PhotoSize::Large)) {
return large->pixSingle(_pixw, _pixh, paintw, painth, roundRadius, roundCorners); return large->pixSingle(_pixw, _pixh, paintw, painth, roundRadius, roundCorners);
@ -801,7 +801,8 @@ bool Photo::needsBubble() const {
} }
const auto item = _parent->data(); const auto item = _parent->data();
if (item->toHistoryMessage()) { if (item->toHistoryMessage()) {
return item->viaBot() return item->repliesAreComments()
|| item->viaBot()
|| _parent->displayedReply() || _parent->displayedReply()
|| _parent->displayForwardedFrom() || _parent->displayForwardedFrom()
|| _parent->displayFromName(); || _parent->displayFromName();

View file

@ -83,7 +83,7 @@ public:
return _caption.isEmpty(); return _caption.isEmpty();
} }
bool skipBubbleTail() const override { bool skipBubbleTail() const override {
return isBubbleBottom() && _caption.isEmpty(); return isRoundedInBubbleBottom() && _caption.isEmpty();
} }
bool isReadyForOpen() const override; bool isReadyForOpen() const override;

View file

@ -1518,7 +1518,7 @@ void Poll::toggleLinkRipple(bool pressed) {
radius); radius);
p.fillRect(0, 0, linkWidth, radius * 2, Qt::white); p.fillRect(0, 0, linkWidth, radius * 2, Qt::white);
}; };
auto mask = isBubbleBottom() auto mask = isRoundedInBubbleBottom()
? Ui::RippleAnimation::maskByDrawer( ? Ui::RippleAnimation::maskByDrawer(
QSize(linkWidth, linkHeight), QSize(linkWidth, linkHeight),
false, false,

View file

@ -1306,13 +1306,7 @@ void MainWidget::viewsIncrementDone(
item->setForwardsCount(forwards->v); item->setForwardsCount(forwards->v);
} }
if (const auto replies = data.vreplies()) { if (const auto replies = data.vreplies()) {
item->setRepliesCount( item->setReplies(*replies);
replies->match([&](const MTPDmessageReplies &data) {
return data.vreplies().v;
}),
replies->match([&](const MTPDmessageReplies &data) {
return data.vreplies_pts().v;
}));
} }
}); });
} }