mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-18 23:27:09 +02:00
Add comments button to channel posts.
This commit is contained in:
parent
ce91caa820
commit
31e1ed216a
33 changed files with 642 additions and 139 deletions
BIN
Telegram/Resources/icons/history_comments.png
Normal file
BIN
Telegram/Resources/icons/history_comments.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 609 B |
BIN
Telegram/Resources/icons/history_comments@2x.png
Normal file
BIN
Telegram/Resources/icons/history_comments@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/history_comments@3x.png
Normal file
BIN
Telegram/Resources/icons/history_comments@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/history_comments_open.png
Normal file
BIN
Telegram/Resources/icons/history_comments_open.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 194 B |
BIN
Telegram/Resources/icons/history_comments_open@2x.png
Normal file
BIN
Telegram/Resources/icons/history_comments_open@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 312 B |
BIN
Telegram/Resources/icons/history_comments_open@3x.png
Normal file
BIN
Telegram/Resources/icons/history_comments_open@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 393 B |
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
|
|
@ -34,10 +34,16 @@ struct HistoryMessageVia : public RuntimeComponent<HistoryMessageVia, HistoryIte
|
|||
};
|
||||
|
||||
struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews, HistoryItem> {
|
||||
QString text;
|
||||
int textWidth = 0;
|
||||
int views = -1;
|
||||
int replies = 0;
|
||||
struct Part {
|
||||
QString text;
|
||||
int textWidth = 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> {
|
||||
|
|
|
@ -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<HistoryMessageViews>()) {
|
||||
return views->views;
|
||||
return std::max(views->views.count, 0);
|
||||
}
|
||||
return HistoryItem::viewsCount();
|
||||
}
|
||||
|
||||
int HistoryMessage::repliesCount() const {
|
||||
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();
|
||||
}
|
||||
|
||||
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() {
|
||||
if (const auto reply = Get<HistoryMessageReply>()) {
|
||||
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<HistoryMessageViews>()) {
|
||||
views->views = config.viewsCount;
|
||||
views->replies = config.repliesCount;
|
||||
setViewsCount(config.viewsCount);
|
||||
if (config.mtpReplies) {
|
||||
setReplies(*config.mtpReplies);
|
||||
}
|
||||
}
|
||||
if (const auto edited = Get<HistoryMessageEdited>()) {
|
||||
edited->date = config.editDate;
|
||||
|
@ -1400,20 +1425,20 @@ bool HistoryMessage::textHasLinks() const {
|
|||
void HistoryMessage::setViewsCount(int count) {
|
||||
const auto views = Get<HistoryMessageViews>();
|
||||
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<HistoryMessageViews>();
|
||||
if (!views) {
|
||||
if (!count) {
|
||||
void HistoryMessage::setReplies(const MTPMessageReplies &data) {
|
||||
data.match([&](const MTPDmessageReplies &data) {
|
||||
auto views = Get<HistoryMessageViews>();
|
||||
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;
|
||||
}
|
||||
AddComponents(HistoryMessageViews::Bit());
|
||||
views = Get<HistoryMessageViews>();
|
||||
}
|
||||
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<HistoryMessageViews*> 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<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) {
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ class Message;
|
|||
|
||||
struct HistoryMessageEdited;
|
||||
struct HistoryMessageReply;
|
||||
struct HistoryMessageViews;
|
||||
|
||||
Fn<void(ChannelData*, MsgId)> HistoryDependentItemCallback(
|
||||
not_null<HistoryItem*> 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<HistoryMessageReply*> reply);
|
||||
void decrementReplyToTopCounter(not_null<HistoryMessageReply*> reply);
|
||||
void refreshRepliesText(
|
||||
not_null<HistoryMessageViews*> views,
|
||||
bool forceResize = false);
|
||||
|
||||
static void FillForwardedInfo(
|
||||
CreateConfig &config,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<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(LogEntryOriginal &&other)
|
||||
|
@ -211,6 +226,13 @@ Message::Message(
|
|||
initPsa();
|
||||
}
|
||||
|
||||
Message::~Message() {
|
||||
if (_comments) {
|
||||
_comments = nullptr;
|
||||
checkHeavyPart();
|
||||
}
|
||||
}
|
||||
|
||||
not_null<HistoryMessage*> Message::message() const {
|
||||
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;
|
||||
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<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(
|
||||
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<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 {
|
||||
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<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(
|
||||
QPoint point,
|
||||
QRect &trect,
|
||||
|
@ -1344,32 +1605,67 @@ void Message::drawInfo(
|
|||
}
|
||||
|
||||
if (auto views = item->Get<HistoryMessageViews>()) {
|
||||
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<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()) {
|
||||
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<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()) {
|
||||
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<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.updateSkipBlock(skipBlockWidth(), skipBlockHeight())) {
|
||||
item->_textWidth = -1;
|
||||
|
|
|
@ -43,6 +43,11 @@ public:
|
|||
not_null<ElementDelegate*> delegate,
|
||||
not_null<HistoryMessage*> 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<HistoryMessage*> 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<TextState*> outResult) const;
|
||||
bool getStateFromName(
|
||||
QPoint point,
|
||||
QRect &trect,
|
||||
|
@ -170,6 +187,7 @@ private:
|
|||
|
||||
mutable ClickHandlerPtr _rightActionLink;
|
||||
mutable ClickHandlerPtr _fastReplyLink;
|
||||
mutable std::unique_ptr<CommentsButton> _comments;
|
||||
int _bubbleWidthLimit = 0;
|
||||
|
||||
};
|
||||
|
|
|
@ -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<QKeyEvent*> 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<RepliesMemento*> 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);
|
||||
|
|
|
@ -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<HistoryItem*> item);
|
||||
void computeCurrentReplyReturn();
|
||||
|
@ -215,7 +219,9 @@ private:
|
|||
|
||||
const not_null<History*> _history;
|
||||
const MsgId _rootId = 0;
|
||||
HistoryItem *_root = nullptr;
|
||||
std::shared_ptr<Data::RepliesList> _replies;
|
||||
rpl::variable<bool> _areComments = false;
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
QPointer<ListWidget> _inner;
|
||||
object_ptr<TopBarWidget> _topBar;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -55,7 +55,7 @@ public:
|
|||
}
|
||||
|
||||
bool skipBubbleTail() const override {
|
||||
return isBubbleBottom();
|
||||
return isRoundedInBubbleBottom();
|
||||
}
|
||||
|
||||
void unloadHeavyPart() override;
|
||||
|
|
|
@ -186,4 +186,8 @@ TextState Media::getStateGrouped(
|
|||
Unexpected("Grouping method call.");
|
||||
}
|
||||
|
||||
bool Media::isRoundedInBubbleBottom() const {
|
||||
return isBubbleBottom() && !_parent->data()->repliesAreComments();
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
||||
|
|
|
@ -217,6 +217,7 @@ public:
|
|||
return (_inBubbleState == MediaInBubbleState::Bottom)
|
||||
|| (_inBubbleState == MediaInBubbleState::None);
|
||||
}
|
||||
[[nodiscard]] bool isRoundedInBubbleBottom() const;
|
||||
[[nodiscard]] virtual bool skipBubbleTail() const {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -83,7 +83,7 @@ public:
|
|||
return _caption.isEmpty();
|
||||
}
|
||||
bool skipBubbleTail() const override {
|
||||
return isBubbleBottom() && _caption.isEmpty();
|
||||
return isRoundedInBubbleBottom() && _caption.isEmpty();
|
||||
}
|
||||
bool isReadyForOpen() const override;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue