mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +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#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";
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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));
|
||||||
});
|
});
|
||||||
|
|
|
@ -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) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -55,7 +55,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skipBubbleTail() const override {
|
bool skipBubbleTail() const override {
|
||||||
return isBubbleBottom();
|
return isRoundedInBubbleBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
void unloadHeavyPart() override;
|
void unloadHeavyPart() override;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue