Extract BottomInfo from HistoryView::Message.

This commit is contained in:
John Preston 2019-09-12 13:35:04 +03:00
parent 3a43217301
commit 1af2cfe143
25 changed files with 561 additions and 365 deletions

View File

@ -621,6 +621,8 @@ PRIVATE
history/view/media/history_view_theme_document.cpp
history/view/media/history_view_web_page.h
history/view/media/history_view_web_page.cpp
history/view/history_view_bottom_info.cpp
history/view/history_view_bottom_info.h
history/view/history_view_contact_status.cpp
history/view/history_view_contact_status.h
history/view/history_view_context_menu.cpp

View File

@ -1163,7 +1163,7 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) {
auto needToAdd = true;
if (d.vmessage().type() == mtpc_message) { // index forwarded messages to links _overview
const auto &data = d.vmessage().c_message();
if (_session->data().checkEntitiesAndViewsUpdate(data)) { // already in blocks
if (_session->data().updateExistingMessage(data)) { // already in blocks
LOG(("Skipping message, because it is already in blocks!"));
needToAdd = false;
}
@ -1257,7 +1257,7 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) {
auto needToAdd = true;
if (d.vmessage().type() == mtpc_message) { // index forwarded messages to links _overview
const auto &data = d.vmessage().c_message();
if (_session->data().checkEntitiesAndViewsUpdate(data)) { // already in blocks
if (_session->data().updateExistingMessage(data)) { // already in blocks
LOG(("Skipping message, because it is already in blocks!"));
needToAdd = false;
}
@ -2163,7 +2163,9 @@ void Updates::feedUpdate(const MTPUpdate &update) {
const auto &d = update.c_updateChannelMessageViews();
const auto peerId = peerFromChannel(d.vchannel_id());
if (const auto item = session().data().message(peerId, d.vid().v)) {
item->setViewsCount(d.vviews().v);
if (item->changeViewsCount(d.vviews().v)) {
session().data().notifyItemDataChange(item);
}
}
} break;

View File

@ -99,7 +99,9 @@ void ViewsManager::done(
if (const auto item = owner.message(peer->id, ids[j].v)) {
v[j].match([&](const MTPDmessageViews &data) {
if (const auto views = data.vviews()) {
item->setViewsCount(views->v);
if (item->changeViewsCount(views->v)) {
_session->data().notifyItemDataChange(item);
}
}
if (const auto forwards = data.vforwards()) {
item->setForwardsCount(forwards->v);

View File

@ -1448,6 +1448,14 @@ rpl::producer<not_null<HistoryItem*>> Session::itemViewRefreshRequest() const {
return _itemViewRefreshRequest.events();
}
void Session::notifyItemDataChange(not_null<HistoryItem*> item) {
_itemDataChanges.fire_copy(item);
}
rpl::producer<not_null<HistoryItem*>> Session::itemDataChanges() const {
return _itemDataChanges.events();
}
void Session::requestItemTextRefresh(not_null<HistoryItem*> item) {
if (const auto i = _views.find(item); i != _views.end()) {
for (const auto view : i->second) {
@ -1806,7 +1814,7 @@ void Session::reorderTwoPinnedChats(
notifyPinnedDialogsOrderUpdated();
}
bool Session::checkEntitiesAndViewsUpdate(const MTPDmessage &data) {
bool Session::updateExistingMessage(const MTPDmessage &data) {
const auto peer = peerFromMTP(data.vpeer_id());
const auto existing = message(peer, data.vid().v);
if (!existing) {
@ -1834,7 +1842,7 @@ void Session::updateEditedMessage(const MTPMessage &data) {
return;
}
if (existing->isLocalUpdateMedia() && data.type() == mtpc_message) {
checkEntitiesAndViewsUpdate(data.c_message());
updateExistingMessage(data.c_message());
}
data.match([](const MTPDmessageEmpty &) {
}, [&](const MTPDmessageService &data) {
@ -1854,7 +1862,7 @@ void Session::processMessages(
const auto &data = message.c_message();
// new message, index my forwarded messages to links overview
if ((type == NewMessageType::Unread)
&& checkEntitiesAndViewsUpdate(data)) {
&& updateExistingMessage(data)) {
continue;
}
}

View File

@ -251,6 +251,8 @@ public:
[[nodiscard]] rpl::producer<not_null<HistoryItem*>> animationPlayInlineRequest() const;
void notifyHistoryUnloaded(not_null<const History*> history);
[[nodiscard]] rpl::producer<not_null<const History*>> historyUnloaded() const;
void notifyItemDataChange(not_null<HistoryItem*> item);
[[nodiscard]] rpl::producer<not_null<HistoryItem*>> itemDataChanges() const;
[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemRemoved() const;
[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemRemoved(
@ -337,7 +339,7 @@ public:
void unregisterMessageTTL(TimeId when, not_null<HistoryItem*> item);
// Returns true if item found and it is not detached.
bool checkEntitiesAndViewsUpdate(const MTPDmessage &data);
bool updateExistingMessage(const MTPDmessage &data);
void updateEditedMessage(const MTPMessage &data);
void processMessages(
const QVector<MTPMessage> &data,
@ -832,6 +834,7 @@ private:
rpl::event_stream<not_null<ViewElement*>> _viewResizeRequest;
rpl::event_stream<not_null<HistoryItem*>> _itemViewRefreshRequest;
rpl::event_stream<not_null<HistoryItem*>> _itemTextRefreshRequest;
rpl::event_stream<not_null<HistoryItem*>> _itemDataChanges;
rpl::event_stream<not_null<HistoryItem*>> _animationPlayInlineRequest;
rpl::event_stream<not_null<const HistoryItem*>> _itemRemoved;
rpl::event_stream<not_null<const ViewElement*>> _viewRemoved;

View File

@ -285,6 +285,12 @@ InnerWidget::InnerWidget(
}
}
}, lifetime());
session().data().itemDataChanges(
) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
if (const auto view = viewForItem(item)) {
view->itemDataChanged();
}
}, lifetime());
session().data().animationPlayInlineRequest(
) | rpl::start_with_next([=](auto item) {
if (const auto view = viewForItem(item)) {

View File

@ -245,6 +245,14 @@ HistoryInner::HistoryInner(
}) | rpl::start_with_next([this](not_null<const Element*> view) {
mouseActionUpdate();
}, lifetime());
session().data().itemDataChanges(
) | rpl::filter([=](not_null<HistoryItem*> item) {
return item->mainView() != nullptr;
}) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
item->mainView()->itemDataChanged();
}, lifetime());
session().changes().historyUpdates(
_history,
Data::HistoryUpdate::Flag::OutboxRead

View File

@ -524,7 +524,7 @@ void HistoryItem::applySentMessage(const MTPDmessage &data) {
}, data.vmedia());
updateReplyMarkup(HistoryMessageMarkupData(data.vreply_markup()));
updateForwardedInfo(data.vfwd_from());
setViewsCount(data.vviews().value_or(-1));
changeViewsCount(data.vviews().value_or(-1));
if (const auto replies = data.vreplies()) {
setReplies(HistoryMessageRepliesData(replies));
} else {
@ -541,6 +541,7 @@ void HistoryItem::applySentMessage(const MTPDmessage &data) {
setPostAuthor(data.vpost_author().value_or_empty());
contributeToSlowmode(data.vdate().v);
indexAsNewItem();
history()->owner().notifyItemDataChange(this);
history()->owner().requestItemTextRefresh(this);
history()->owner().updateDependentMessages(this);
}
@ -596,6 +597,7 @@ void HistoryItem::setRealId(MsgId newId) {
}
}
_history->owner().notifyItemDataChange(this);
_history->owner().requestItemRepaint(this);
}
@ -914,6 +916,7 @@ void HistoryItem::sendFailed() {
Expects(!(_flags & MessageFlag::SendingFailed));
_flags = (_flags | MessageFlag::SendingFailed) & ~MessageFlag::BeingSent;
_history->owner().notifyItemDataChange(this);
history()->session().changes().historyUpdated(
history(),
Data::HistoryUpdate::Flag::ClientSideMessages);

View File

@ -352,7 +352,8 @@ public:
return TextForMimeData();
}
virtual void setViewsCount(int count) {
virtual bool changeViewsCount(int count) {
return false;
}
virtual void setForwardsCount(int count) {
}

View File

@ -84,38 +84,6 @@ void HistoryMessageVia::resize(int32 availw) const {
}
}
void HistoryMessageSigned::refresh(const QString &date) {
Expects(!isAnonymousRank);
auto name = author;
const auto time = qsl(", ") + date;
const auto timew = st::msgDateFont->width(time);
const auto namew = st::msgDateFont->width(name);
isElided = (timew + namew > st::maxSignatureSize);
if (isElided) {
name = st::msgDateFont->elided(author, st::maxSignatureSize - timew);
}
signature.setText(
st::msgDateTextStyle,
name + time,
Ui::NameTextOptions());
}
int HistoryMessageSigned::maxWidth() const {
return signature.maxWidth();
}
void HistoryMessageEdited::refresh(const QString &date, bool displayed) {
const auto prefix = displayed
? (tr::lng_edited(tr::now) + ' ')
: QString();
text.setText(st::msgDateTextStyle, prefix + date, Ui::NameTextOptions());
}
int HistoryMessageEdited::maxWidth() const {
return text.maxWidth();
}
HistoryMessageSponsored::HistoryMessageSponsored() {
text.setText(
st::msgDateTextStyle,

View File

@ -60,21 +60,12 @@ struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews, Histor
};
struct HistoryMessageSigned : public RuntimeComponent<HistoryMessageSigned, HistoryItem> {
void refresh(const QString &date);
int maxWidth() const;
QString author;
Ui::Text::String signature;
bool isElided = false;
bool isAnonymousRank = false;
};
struct HistoryMessageEdited : public RuntimeComponent<HistoryMessageEdited, HistoryItem> {
void refresh(const QString &date, bool displayed);
int maxWidth() const;
TimeId date = 0;
Ui::Text::String text;
};
struct HistoryMessageSponsored : public RuntimeComponent<

View File

@ -1124,7 +1124,7 @@ void HistoryMessage::createComponents(CreateConfig &&config) {
via->create(&history()->owner(), config.viaBotId);
}
if (const auto views = Get<HistoryMessageViews>()) {
setViewsCount(config.viewsCount);
changeViewsCount(config.viewsCount);
if (config.replies.isNull
&& isSending()
&& config.markup.isNull()) {
@ -1423,7 +1423,7 @@ void HistoryMessage::applyEdition(HistoryMessageEdition &&edition) {
if (!isLocalUpdateMedia()) {
refreshMedia(edition.mtpMedia);
}
setViewsCount(edition.views);
changeViewsCount(edition.views);
setForwardsCount(edition.forwards);
setText(_media
? edition.textWithEntities
@ -1449,7 +1449,7 @@ void HistoryMessage::applyEdition(const MTPDmessageService &message) {
setReplyMarkup({});
refreshMedia(nullptr);
setEmptyText();
setViewsCount(-1);
changeViewsCount(-1);
setForwardsCount(-1);
if (wasGrouped) {
history()->owner().groups().unregisterMessage(this);
@ -1744,27 +1744,16 @@ bool HistoryMessage::textHasLinks() const {
return emptyText() ? false : _text.hasLinks();
}
void HistoryMessage::setViewsCount(int count) {
bool HistoryMessage::changeViewsCount(int count) {
const auto views = Get<HistoryMessageViews>();
if (!views
|| views->views.count == count
|| (count >= 0 && views->views.count > count)) {
return;
return false;
}
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->views.text);
if (was == views->views.textWidth) {
history()->owner().requestItemRepaint(this);
} else {
history()->owner().requestItemResize(this);
}
return true;
}
void HistoryMessage::setForwardsCount(int count) {
@ -1852,7 +1841,6 @@ void HistoryMessage::clearReplies() {
void HistoryMessage::refreshRepliesText(
not_null<HistoryMessageViews*> views,
bool forceResize) {
const auto was = views->replies.textWidth;
if (views->commentsMegagroupId) {
views->replies.text = (views->replies.count > 0)
? tr::lng_comments_open_count(
@ -1867,15 +1855,8 @@ void HistoryMessage::refreshRepliesText(
: QString();
views->repliesSmall.textWidth = st::semiboldFont->width(
views->repliesSmall.text);
} else {
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) {
if (forceResize) {
history()->owner().requestItemResize(this);
} else {
history()->owner().requestItemRepaint(this);

View File

@ -129,7 +129,7 @@ public:
[[nodiscard]] bool allowsSendNow() const override;
[[nodiscard]] bool allowsEdit(TimeId now) const override;
void setViewsCount(int count) override;
bool changeViewsCount(int count) override;
void setForwardsCount(int count) override;
void setReplies(HistoryMessageRepliesData &&data) override;
void clearReplies() override;
@ -263,10 +263,7 @@ private:
[[nodiscard]] bool checkRepliesPts(
const HistoryMessageRepliesData &data) const;
QString _timeText;
int _timeWidth = 0;
mutable int32 _fromNameVersion = 0;
mutable int _fromNameVersion = 0;
friend class HistoryView::Element;
friend class HistoryView::Message;

View File

@ -507,6 +507,13 @@ HistoryWidget::HistoryWidget(
}
}, lifetime());
session().data().itemDataChanges(
) | rpl::filter([=](not_null<HistoryItem*> item) {
return !_list && (item->mainView() != nullptr);
}) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
item->mainView()->itemDataChanged();
}, lifetime());
Core::App().settings().largeEmojiChanges(
) | rpl::start_with_next([=] {
crl::on_main(this, [=] {

View File

@ -0,0 +1,303 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/history_view_bottom_info.h"
#include "ui/chat/message_bubble.h"
#include "ui/chat/chat_style.h"
#include "ui/text/text_options.h"
#include "lang/lang_keys.h"
#include "history/history_item_components.h"
#include "history/history_message.h"
#include "history/view/history_view_message.h"
#include "styles/style_chat.h"
#include "styles/style_dialogs.h"
namespace HistoryView {
BottomInfo::BottomInfo(Data &&data) : _data(std::move(data)) {
layout();
}
void BottomInfo::update(Data &&data) {
_data = std::move(data);
layout();
if (!_size.isEmpty()) {
resizeToWidth(_size.width());
}
}
QSize BottomInfo::optimalSize() const {
return _optimalSize;
}
QSize BottomInfo::size() const {
return _size;
}
bool BottomInfo::pointInTime(QPoint position) const {
return QRect(
_size.width() - _dateWidth,
0,
_dateWidth,
st::msgDateFont->height
).contains(position);
}
bool BottomInfo::isSignedAuthorElided() const {
return _authorElided;
}
void BottomInfo::paint(
Painter &p,
QPoint position,
int outerWidth,
bool unread,
bool inverted,
const PaintContext &context) const {
const auto st = context.st;
const auto sti = context.imageStyle();
const auto stm = context.messageStyle();
auto right = position.x() + _size.width();
const auto firstLineBottom = position.y() + st::msgDateFont->height;
if (_data.flags & Data::Flag::OutLayout) {
const auto &icon = (_data.flags & Data::Flag::Sending)
? (inverted
? st->historySendingInvertedIcon()
: st->historySendingIcon())
: unread
? (inverted
? st->historySentInvertedIcon()
: stm->historySentIcon)
: (inverted
? st->historyReceivedInvertedIcon()
: stm->historyReceivedIcon);
icon.paint(
p,
QPoint(right, firstLineBottom) + st::historySendStatePosition,
outerWidth);
right -= st::historySendStateSpace;
}
const auto authorEditedWidth = _authorEditedDate.maxWidth();
right -= authorEditedWidth;
_authorEditedDate.drawLeft(
p,
right,
position.y(),
authorEditedWidth,
outerWidth);
if (!_views.isEmpty()) {
const auto viewsWidth = _views.maxWidth();
right -= st::historyViewsSpace + viewsWidth;
_views.drawLeft(p, right, position.y(), viewsWidth, outerWidth);
const auto &icon = inverted
? st->historyViewsInvertedIcon()
: stm->historyViewsIcon;
icon.paint(
p,
right - st::historyViewsWidth,
firstLineBottom + st::historyViewsTop,
outerWidth);
}
if (!_replies.isEmpty()) {
const auto repliesWidth = _replies.maxWidth();
right -= st::historyViewsSpace + repliesWidth;
_replies.drawLeft(p, right, position.y(), repliesWidth, outerWidth);
const auto &icon = inverted
? st->historyRepliesInvertedIcon()
: stm->historyRepliesIcon;
icon.paint(
p,
right - st::historyViewsWidth,
firstLineBottom + st::historyViewsTop,
outerWidth);
}
if ((_data.flags & Data::Flag::Sending)
&& !(_data.flags & Data::Flag::OutLayout)) {
right -= st::historySendStateSpace;
const auto &icon = inverted
? st->historyViewsSendingInvertedIcon()
: st->historyViewsSendingIcon();
icon.paint(
p,
right,
firstLineBottom + st::historyViewsTop,
outerWidth);
}
if (!_reactions.isEmpty()) {
if (_size.height() == _optimalSize.height()) {
_reactions.drawLeft(
p,
position.x(),
position.y(),
_reactions.maxWidth(),
outerWidth);
} else {
const auto available = _size.width();
const auto use = std::min(available, _reactions.maxWidth());
_reactions.drawLeftElided(
p,
position.x() + _size.width() - use,
position.y() + st::msgDateFont->height,
use,
outerWidth);
}
}
}
int BottomInfo::resizeToWidth(int newWidth) {
if (newWidth >= _optimalSize.width()) {
_size = _optimalSize;
return _size.height();
}
return 2 * st::msgDateFont->height;
}
void BottomInfo::layout() {
layoutDateText();
layoutViewsText();
layoutReactionsText();
countOptimalSize();
}
void BottomInfo::layoutDateText() {
const auto edited = (_data.flags & Data::Flag::Edited)
? (tr::lng_edited(tr::now) + ' ')
: QString();
const auto author = _data.author;
const auto prefix = author.isEmpty() ? qsl(", ") : QString();
const auto date = _data.date.toString(cTimeFormat());
_dateWidth = st::msgDateFont->width(date);
const auto afterAuthor = prefix + edited + date;
const auto afterAuthorWidth = st::msgDateFont->width(afterAuthor);
const auto authorWidth = st::msgDateFont->width(author);
const auto maxWidth = st::maxSignatureSize;
_authorElided = !author.isEmpty()
&& (authorWidth + afterAuthorWidth > maxWidth);
const auto name = _authorElided
? st::msgDateFont->elided(author, maxWidth - afterAuthorWidth)
: author;
const auto full = name + date;
_authorEditedDate.setText(
st::msgDateTextStyle,
full,
Ui::NameTextOptions());
}
void BottomInfo::layoutViewsText() {
if (!_data.views || (_data.flags & Data::Flag::Sending)) {
_views.clear();
return;
}
_views.setText(
st::msgDateTextStyle,
Lang::FormatCountToShort(std::max(*_data.views, 1)).string,
Ui::NameTextOptions());
}
void BottomInfo::layoutRepliesText() {
if (!_data.replies
|| !*_data.replies
|| (_data.flags & Data::Flag::RepliesContext)
|| (_data.flags & Data::Flag::Sending)) {
_replies.clear();
return;
}
_replies.setText(
st::msgDateTextStyle,
Lang::FormatCountToShort(*_data.replies).string,
Ui::NameTextOptions());
}
void BottomInfo::layoutReactionsText() {
if (_data.reactions.empty()) {
_reactions.clear();
return;
}
auto sorted = ranges::view::all(
_data.reactions
) | ranges::view::transform([](const auto &pair) {
return std::make_pair(pair.first, pair.second);
}) | ranges::to_vector;
ranges::sort(sorted, std::greater<>(), &std::pair<QString, int>::second);
auto fullCount = 0;
auto text = QString();
for (const auto &[string, count] : sorted) {
text.append(string);
fullCount += count;
}
text += QString::number(fullCount);
_reactions.setText(st::msgDateTextStyle, text, Ui::NameTextOptions());
}
void BottomInfo::countOptimalSize() {
auto width = 0;
if (_data.flags & (Data::Flag::OutLayout | Data::Flag::Sending)) {
width += st::historySendStateSpace;
}
width += _authorEditedDate.maxWidth();
if (!_views.isEmpty()) {
width += st::historyViewsSpace
+ _views.maxWidth()
+ st::historyViewsWidth;
}
if (!_reactions.isEmpty()) {
width += st::historyReactionsSkip + _reactions.maxWidth();
}
_optimalSize = QSize(width, st::msgDateFont->height);
}
BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
using Flag = BottomInfo::Data::Flag;
auto result = BottomInfo::Data();
const auto item = message->message();
result.date = message->dateTime();
result.reactions = item->reactions();
if (message->hasOutLayout()) {
result.flags |= Flag::OutLayout;
}
if (message->context() == Context::Replies) {
result.flags |= Flag::RepliesContext;
}
if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
if (!msgsigned->isAnonymousRank) {
result.author = msgsigned->author;
}
}
if (!item->hideEditedBadge()) {
if (const auto edited = message->displayedEditBadge()) {
result.flags |= Flag::Edited;
}
}
if (const auto views = item->Get<HistoryMessageViews>()) {
if (views->views.count >= 0) {
result.views = views->views.count;
}
if (views->replies.count >= 0 && !views->commentsMegagroupId) {
result.replies = views->replies.count;
}
}
if (item->isSending() || item->hasFailed()) {
result.flags |= Flag::Sending;
}
// We don't want to pass and update it in Date for now.
//if (item->unread()) {
// result.flags |= Flag::Unread;
//}
return result;
}
} // namespace HistoryView

View File

@ -0,0 +1,85 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/text/text.h"
#include "base/flags.h"
namespace Ui {
struct ChatPaintContext;
} // namespace Ui
namespace HistoryView {
using PaintContext = Ui::ChatPaintContext;
class Message;
class BottomInfo {
public:
struct Data {
enum class Flag {
Edited = 0x01,
OutLayout = 0x02,
Sending = 0x04,
RepliesContext = 0x08,
//Unread, // We don't want to pass and update it in Date for now.
};
friend inline constexpr bool is_flag_type(Flag) { return true; };
using Flags = base::flags<Flag>;
QDateTime date;
QString author;
base::flat_map<QString, int> reactions;
std::optional<int> views;
std::optional<int> replies;
Flags flags;
};
explicit BottomInfo(Data &&data);
void update(Data &&data);
[[nodiscard]] QSize optimalSize() const;
[[nodiscard]] QSize size() const;
[[nodiscard]] bool pointInTime(QPoint position) const;
[[nodiscard]] bool isSignedAuthorElided() const;
void paint(
Painter &p,
QPoint position,
int outerWidth,
bool unread,
bool inverted,
const PaintContext &context) const;
int resizeToWidth(int newWidth);
private:
void layout();
void layoutDateText();
void layoutViewsText();
void layoutRepliesText();
void layoutReactionsText();
void countOptimalSize();
Data _data;
QSize _optimalSize;
QSize _size;
Ui::Text::String _authorEditedDate;
Ui::Text::String _views;
Ui::Text::String _replies;
Ui::Text::String _reactions;
int _dateWidth = 0;
bool _authorElided = false;
};
[[nodiscard]] BottomInfo::Data BottomInfoDataFromMessage(
not_null<Message*> message);
} // namespace HistoryView

View File

@ -231,8 +231,8 @@ QString DateTooltipText(not_null<Element*> view) {
+ "\n\n" + dateText;
}
}
if (const auto msgsigned = view->data()->Get<HistoryMessageSigned>()) {
if (msgsigned->isElided && !msgsigned->isAnonymousRank) {
if (view->isSignedAuthorElided()) {
if (const auto msgsigned = view->data()->Get<HistoryMessageSigned>()) {
dateText += '\n'
+ tr::lng_signed_author(tr::now, lt_user, msgsigned->author);
}
@ -841,6 +841,13 @@ void Element::checkHeavyPart() {
}
}
bool Element::isSignedAuthorElided() const {
return false;
}
void Element::itemDataChanged() {
}
void Element::unloadHeavyPart() {
history()->owner().unregisterHeavyViewPart(this);
if (_media) {

View File

@ -318,31 +318,31 @@ public:
// 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
virtual bool hasFromPhoto() const;
virtual bool displayFromPhoto() const;
virtual bool hasFromName() const;
virtual bool displayFromName() const;
virtual bool displayForwardedFrom() const;
virtual bool hasOutLayout() const;
virtual bool drawBubble() const;
virtual bool hasBubble() const;
virtual int minWidthForMedia() const {
[[nodiscard]] virtual bool hasFromPhoto() const;
[[nodiscard]] virtual bool displayFromPhoto() const;
[[nodiscard]] virtual bool hasFromName() const;
[[nodiscard]] virtual bool displayFromName() const;
[[nodiscard]] virtual bool displayForwardedFrom() const;
[[nodiscard]] virtual bool hasOutLayout() const;
[[nodiscard]] virtual bool drawBubble() const;
[[nodiscard]] virtual bool hasBubble() const;
[[nodiscard]] virtual int minWidthForMedia() const {
return 0;
}
virtual bool hasFastReply() const;
virtual bool displayFastReply() const;
virtual std::optional<QSize> rightActionSize() const;
[[nodiscard]] virtual bool hasFastReply() const;
[[nodiscard]] virtual bool displayFastReply() const;
[[nodiscard]] virtual std::optional<QSize> rightActionSize() const;
virtual void drawRightAction(
Painter &p,
const PaintContext &context,
int left,
int top,
int outerWidth) const;
virtual ClickHandlerPtr rightActionLink() const;
virtual bool displayEditedBadge() const;
virtual TimeId displayedEditDate() const;
virtual bool hasVisibleText() const;
virtual HistoryMessageReply *displayedReply() const;
[[nodiscard]] virtual ClickHandlerPtr rightActionLink() const;
[[nodiscard]] virtual bool displayEditedBadge() const;
[[nodiscard]] virtual TimeId displayedEditDate() const;
[[nodiscard]] virtual bool hasVisibleText() const;
[[nodiscard]] virtual HistoryMessageReply *displayedReply() const;
virtual void applyGroupAdminChanges(
const base::flat_set<UserId> &changes) {
}
@ -355,6 +355,10 @@ public:
};
[[nodiscard]] virtual VerticalRepaintRange verticalRepaintRange() const;
[[nodiscard]] virtual bool isSignedAuthorElided() const;
virtual void itemDataChanged();
virtual bool hasHeavyPart() const;
virtual void unloadHeavyPart();
void checkHeavyPart();

View File

@ -294,6 +294,12 @@ ListWidget::ListWidget(
}
}
}, lifetime());
session().data().itemDataChanges(
) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
if (const auto view = viewForItem(item)) {
view->itemDataChanged();
}
}, lifetime());
session().data().animationPlayInlineRequest(
) | rpl::start_with_next([this](auto item) {
if (const auto view = viewForItem(item)) {

View File

@ -237,30 +237,12 @@ LogEntryOriginal &LogEntryOriginal::operator=(LogEntryOriginal &&other) {
LogEntryOriginal::~LogEntryOriginal() = default;
void Reactions::update(const base::flat_map<QString, int> &list) {
auto sorted = ranges::view::all(
list
) | ranges::view::transform([](const auto &pair) {
return std::make_pair(pair.first, pair.second);
}) | ranges::to_vector;
ranges::sort(sorted, std::greater<>(), &std::pair<QString, int>::second);
auto fullCount = 0;
auto composed = QString();
for (const auto &[string, count] : sorted) {
composed.append(string);
fullCount += count;
}
composed += QString::number(fullCount);
text.setText(st::msgDateTextStyle, composed, Ui::NameTextOptions());
}
Message::Message(
not_null<ElementDelegate*> delegate,
not_null<HistoryMessage*> data,
Element *replacing)
: Element(delegate, data, replacing) {
: Element(delegate, data, replacing)
, _bottomInfo(BottomInfoDataFromMessage(this)) {
initLogEntryOriginal();
initPsa();
}
@ -334,16 +316,8 @@ QSize Message::performCountOptimalSize() {
updateViewButtonExistence();
updateMediaInBubbleState();
refreshEditedBadge();
refreshRightBadge();
if (const auto list = item->reactions(); !list.empty()) {
AddComponents(Reactions::Bit());
const auto reactions = Get<Reactions>();
reactions->update(list);
} else {
RemoveComponents(Reactions::Bit());
}
initTime();
if (drawBubble()) {
const auto forwarded = item->Get<HistoryMessageForwarded>();
@ -1776,111 +1750,25 @@ void Message::drawInfo(
break;
}
const auto item = message();
auto infoW = infoWidth();
if (rtl()) infoRight = width - infoRight + infoW;
auto dateX = infoRight - infoW;
auto dateY = infoBottom - st::msgDateFont->height;
const auto size = _bottomInfo.size();
const auto dateX = infoRight - size.width();
const auto dateY = infoBottom - size.height();
if (type == InfoDisplayType::Image) {
auto dateW = infoW + 2 * st::msgDateImgPadding.x(), dateH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y();
const auto dateW = size.width() + 2 * st::msgDateImgPadding.x();
const auto dateH = size.height() + 2 * st::msgDateImgPadding.y();
Ui::FillRoundRect(p, dateX - st::msgDateImgPadding.x(), dateY - st::msgDateImgPadding.y(), dateW, dateH, sti->msgDateImgBg, sti->msgDateImgBgCorners);
} else if (type == InfoDisplayType::Background) {
auto dateW = infoW + 2 * st::msgDateImgPadding.x(), dateH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y();
const auto dateW = size.width() + 2 * st::msgDateImgPadding.x();
const auto dateH = size.height() + 2 * st::msgDateImgPadding.y();
Ui::FillRoundRect(p, dateX - st::msgDateImgPadding.x(), dateY - st::msgDateImgPadding.y(), dateW, dateH, sti->msgServiceBg, sti->msgServiceBgCorners);
}
dateX += timeLeft();
if (const auto msgsigned = item->Get<HistoryMessageSigned>()
; msgsigned && !msgsigned->isAnonymousRank) {
msgsigned->signature.drawElided(p, dateX, dateY, item->_timeWidth);
} else if (const auto sponsored = displayedSponsorBadge()) {
const auto skipY = viewButtonHeight();
sponsored->text.drawElided(p, dateX, dateY - skipY, item->_timeWidth);
} else if (const auto edited = displayedEditBadge()) {
edited->text.drawElided(p, dateX, dateY, item->_timeWidth);
} else {
p.drawText(dateX, dateY + st::msgDateFont->ascent, item->_timeText);
}
const auto viewIconTop = infoBottom + st::historyViewsTop;
const auto pinIconTop = infoBottom + st::historyPinTop;
auto left = infoRight - infoW;
if (const auto reactions = Get<Reactions>()) {
reactions->text.draw(p, left, dateY, reactions->text.maxWidth());
left += reactions->text.maxWidth() + st::historyReactionsSkip;
}
if (const auto views = item->Get<HistoryMessageViews>()) {
const auto textTop = infoBottom - st::msgDateFont->descent;
if (views->replies.count > 0
&& !views->commentsMegagroupId
&& this->context() != Context::Replies) {
const auto &icon = (!item->isSending() && !item->hasFailed())
? (invertedsprites
? st->historyRepliesInvertedIcon()
: stm->historyRepliesIcon)
: (invertedsprites
? st->historyViewsSendingInvertedIcon()
: st->historyViewsSendingIcon());
if (!item->isSending() && !item->hasFailed()) {
icon.paint(p, left, viewIconTop, width);
p.drawText(left + st::historyViewsWidth, textTop, views->replies.text);
} else if (!context.outbg && views->views.count < 0) { // sending outbg icon will be painted below
auto iconSkip = st::historyViewsSpace + views->replies.textWidth;
icon.paint(p, left + iconSkip, viewIconTop, width);
}
left += st::historyViewsSpace
+ views->replies.textWidth
+ st::historyViewsWidth;
}
if (views->views.count >= 0) {
const auto &icon = (!item->isSending() && !item->hasFailed())
? (invertedsprites
? st->historyViewsInvertedIcon()
: stm->historyViewsIcon)
: (invertedsprites
? st->historyViewsSendingInvertedIcon()
: st->historyViewsSendingIcon());
if (!item->isSending() && !item->hasFailed()) {
icon.paint(p, left, viewIconTop, width);
p.drawText(left + st::historyViewsWidth, textTop, views->views.text);
} else if (!context.outbg) { // sending outbg icon will be painted below
auto iconSkip = st::historyViewsSpace + views->views.textWidth;
icon.paint(p, left + iconSkip, viewIconTop, width);
}
left += st::historyViewsSpace
+ views->views.textWidth
+ st::historyViewsWidth;
}
} else if ((item->isSending() || item->hasFailed())
&& item->history()->peer->isSelf()
&& !context.outbg) {
const auto &icon = invertedsprites
? st->historyViewsSendingInvertedIcon()
: st->historyViewsSendingIcon();
icon.paint(p, left, viewIconTop, width);
}
if (displayPinIcon()) {
const auto &icon = invertedsprites
? st->historyPinInvertedIcon()
: stm->historyPinIcon;
icon.paint(p, left, pinIconTop, width);
left += st::historyPinWidth;
}
if (context.outbg) {
const auto &icon = (item->isSending() || item->hasFailed())
? (invertedsprites
? st->historySendingInvertedIcon()
: st->historySendingIcon())
: delegate()->elementShownUnread(this)
? (invertedsprites
? st->historySentInvertedIcon()
: stm->historySentIcon)
: (invertedsprites
? st->historyReceivedInvertedIcon()
: stm->historyReceivedIcon);
icon.paint(p, QPoint(infoRight, infoBottom) + st::historySendStatePosition, width);
}
_bottomInfo.paint(
p,
{ dateX, dateY },
width,
delegate()->elementShownUnread(this),
invertedsprites,
context);
}
bool Message::pointInTime(
@ -1904,53 +1792,28 @@ bool Message::pointInTime(
infoBottom -= st::msgDateImgPadding.y();
break;
}
const auto item = message();
auto dateX = infoRight - infoWidth() + timeLeft();
auto dateY = infoBottom - st::msgDateFont->height;
return QRect(
dateX,
dateY,
item->_timeWidth,
st::msgDateFont->height).contains(point);
const auto size = _bottomInfo.size();
const auto infoLeft = infoRight - size.width();
const auto infoTop = infoBottom - size.height();
return _bottomInfo.pointInTime({ infoLeft, infoTop });
}
int Message::infoWidth() const {
const auto item = message();
auto result = item->_timeWidth;
if (const auto views = item->Get<HistoryMessageViews>()) {
if (views->views.count >= 0) {
result += st::historyViewsSpace
+ views->views.textWidth
+ st::historyViewsWidth;
}
if (views->replies.count > 0
&& !views->commentsMegagroupId
&& context() != Context::Replies) {
result += st::historyViewsSpace
+ views->replies.textWidth
+ st::historyViewsWidth;
}
} else if ((item->isSending() || item->hasFailed())
&& item->history()->peer->isSelf()) {
if (!hasOutLayout()) {
result += st::historySendStateSpace;
}
}
if (displayPinIcon()) {
result += st::historyPinWidth;
}
return _bottomInfo.optimalSize().width();
}
// When message is scheduled until online, time is not displayed,
// so message should have less space.
if (!item->_timeWidth) {
result += st::historyScheduledUntilOnlineStateSpace;
} else if (hasOutLayout()) {
result += st::historySendStateSpace;
bool Message::isSignedAuthorElided() const {
return _bottomInfo.isSignedAuthorElided();
}
void Message::itemDataChanged() {
const auto was = _bottomInfo.size();
_bottomInfo.update(BottomInfoDataFromMessage(this));
if (was != _bottomInfo.size()) {
history()->owner().requestViewResize(this);
} else {
history()->owner().requestViewRepaint(this);
}
if (const auto reactions = Get<Reactions>()) {
result += st::historyReactionsSkip + reactions->text.maxWidth();
}
return result;
}
auto Message::verticalRepaintRange() const -> VerticalRepaintRange {
@ -1974,34 +1837,6 @@ void Message::refreshDataIdHook() {
}
}
int Message::timeLeft() const {
const auto item = message();
auto result = 0;
if (auto views = item->Get<HistoryMessageViews>()) {
if (views->views.count >= 0) {
result += st::historyViewsSpace + views->views.textWidth + st::historyViewsWidth;
}
if (views->replies.count > 0
&& !views->commentsMegagroupId
&& context() != Context::Replies) {
result += st::historyViewsSpace + views->replies.textWidth + st::historyViewsWidth;
}
} else if ((item->isSending() || item->hasFailed())
&& item->history()->peer->isSelf()) {
if (!hasOutLayout()) {
result += st::historySendStateSpace;
}
}
if (displayPinIcon()) {
result += st::historyPinWidth;
}
if (const auto reactions = Get<Reactions>()) {
result += st::historyReactionsSkip + reactions->text.maxWidth();
}
return result;
}
int Message::plainMaxWidth() const {
return st::msgPadding.left()
+ (hasVisibleText() ? message()->_text.maxWidth() : 0)
@ -2629,6 +2464,8 @@ int Message::resizeContentGetHeight(int newWidth) {
}
}
}
_bottomInfo.resizeToWidth(
std::min(_bottomInfo.optimalSize().width(), contentWidth));
if (bubble) {
auto reply = displayedReply();
@ -2733,46 +2570,8 @@ QSize Message::performCountCurrentSize(int newWidth) {
return { newWidth, newHeight };
}
void Message::refreshEditedBadge() {
const auto item = message();
const auto edited = displayedEditBadge();
const auto editDate = displayedEditDate();
const auto dateText = dateTime().toString(cTimeFormat());
if (edited) {
edited->refresh(dateText, editDate != 0);
}
if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
if (!msgsigned->isAnonymousRank) {
const auto text = (!edited || !editDate)
? dateText
: edited->text.toString();
msgsigned->refresh(text);
}
}
initTime();
}
void Message::initTime() const {
const auto item = message();
if (const auto msgsigned = item->Get<HistoryMessageSigned>()
; msgsigned && !msgsigned->isAnonymousRank) {
item->_timeWidth = msgsigned->maxWidth();
} else if (const auto sponsored = displayedSponsorBadge()) {
item->_timeWidth = sponsored->maxWidth();
} else if (const auto edited = displayedEditBadge()) {
item->_timeWidth = edited->maxWidth();
} else {
const auto forwarded = item->Get<HistoryMessageForwarded>();
if (forwarded && forwarded->imported) {
const auto date = base::unixtime::parse(forwarded->originalDate);
item->_timeText = date.toString(
cDateFormat() + u", "_q + cTimeFormat() + ' '
) + tr::lng_imported(tr::now);
} else {
item->_timeText = dateTime().toString(cTimeFormat());
}
item->_timeWidth = st::msgDateFont->width(item->_timeText);
}
if (item->_text.hasSkipBlock()) {
if (item->_text.updateSkipBlock(skipBlockWidth(), skipBlockHeight())) {
item->_textWidth = -1;

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "history/view/history_view_element.h"
#include "history/view/history_view_bottom_info.h"
#include "ui/effects/animations.h"
#include "base/weak_ptr.h"
@ -39,12 +40,6 @@ struct PsaTooltipState : public RuntimeComponent<PsaTooltipState, Element> {
mutable bool buttonVisible = true;
};
struct Reactions : public RuntimeComponent<Reactions, Element> {
void update(const base::flat_map<QString, int> &list);
Ui::Text::String text;
};
class Message : public Element, public base::has_weak_ptr {
public:
Message(
@ -57,6 +52,11 @@ public:
const ClickHandlerPtr &handler,
bool pressed) override;
not_null<HistoryMessage*> message() const;
const HistoryMessageEdited *displayedEditBadge() const;
HistoryMessageEdited *displayedEditBadge();
int marginTop() const override;
int marginBottom() const override;
void draw(Painter &p, const PaintContext &context) const override;
@ -113,6 +113,9 @@ public:
bool toggleSelectionByHandlerClick(
const ClickHandlerPtr &handler) const override;
int infoWidth() const override;
bool isSignedAuthorElided() const override;
void itemDataChanged() override;
VerticalRepaintRange verticalRepaintRange() const override;
@ -125,11 +128,8 @@ protected:
private:
struct CommentsButton;
not_null<HistoryMessage*> message() const;
void initLogEntryOriginal();
void initPsa();
void refreshEditedBadge();
void fromNameUpdated(int width) const;
[[nodiscard]] bool showForwardsFromSender(
@ -208,14 +208,11 @@ private:
[[nodiscard]] bool displayFastShare() const;
[[nodiscard]] bool displayGoToOriginal() const;
[[nodiscard]] ClickHandlerPtr fastReplyLink() const;
[[nodiscard]] const HistoryMessageEdited *displayedEditBadge() const;
[[nodiscard]] HistoryMessageEdited *displayedEditBadge();
[[nodiscard]] auto displayedSponsorBadge() const
-> const HistoryMessageSponsored*;
[[nodiscard]] bool displayPinIcon() const;
void initTime() const;
[[nodiscard]] int timeLeft() const;
[[nodiscard]] int plainMaxWidth() const;
[[nodiscard]] int monospaceMaxWidth() const;
@ -238,6 +235,8 @@ private:
Ui::Text::String _rightBadge;
int _bubbleWidthLimit = 0;
BottomInfo _bottomInfo;
};
} // namespace HistoryView

View File

@ -154,7 +154,10 @@ QSize Gif::countOptimalSize() {
}
_thumbw = tw;
_thumbh = th;
auto maxWidth = qMax(tw, st::minPhotoSize);
auto maxWidth = std::clamp(
std::max(tw, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())),
st::minPhotoSize,
maxSize);
auto minHeight = qMax(th, st::minPhotoSize);
if (!activeCurrentStreamed()) {
accumulate_max(maxWidth, gifMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
@ -211,7 +214,10 @@ QSize Gif::countCurrentSize(int newWidth) {
_thumbw = tw;
_thumbh = th;
newWidth = qMax(tw, st::minPhotoSize);
newWidth = std::clamp(
std::max(tw, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())),
st::minPhotoSize,
maxSize);
auto newHeight = qMax(th, st::minPhotoSize);
if (!activeCurrentStreamed()) {
accumulate_max(newWidth, gifMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));

View File

@ -82,7 +82,10 @@ QSize Location::countOptimalSize() {
th = (st::maxMediaSize * th) / tw;
tw = st::maxMediaSize;
}
auto minWidth = qMax(st::minPhotoSize, _parent->minWidthForMedia());
auto minWidth = std::clamp(
_parent->minWidthForMedia(),
st::minPhotoSize,
st::maxMediaSize);
auto maxWidth = qMax(tw, minWidth);
auto minHeight = qMax(th, st::minPhotoSize);
@ -118,7 +121,10 @@ QSize Location::countCurrentSize(int newWidth) {
} else {
newWidth = tw;
}
auto minWidth = qMax(st::minPhotoSize, _parent->minWidthForMedia());
auto minWidth = std::clamp(
_parent->minWidthForMedia(),
st::minPhotoSize,
st::maxMediaSize);
accumulate_max(newWidth, minWidth);
accumulate_max(newHeight, st::minPhotoSize);
if (_parent->hasBubble()) {

View File

@ -408,19 +408,19 @@ int UnwrappedMedia::calculateFullRight(const QRect &inner) const {
+ st::msgDateImgPadding.x() * 2
+ st::msgReplyPadding.left();
const auto rightActionSize = _parent->rightActionSize();
const auto rightSkip = st::msgPadding.left()
+ (_parent->hasFromPhoto()
? st::msgMargin.right()
: st::msgPadding.right());
const auto rightActionWidth = rightActionSize
? (st::historyFastShareLeft * 2
+ rightActionSize->width()
+ st::msgPadding.left()
+ (_parent->hasFromPhoto()
? st::msgMargin.right()
: st::msgPadding.right()))
+ rightActionSize->width())
: 0;
auto fullRight = inner.x()
+ inner.width()
+ (rightAligned ? 0 : infoWidth);
if (fullRight + rightActionWidth > _parent->width()) {
fullRight = _parent->width() - rightActionWidth;
if (fullRight + rightActionWidth + rightSkip > _parent->width()) {
fullRight = _parent->width() - rightActionWidth - rightSkip;
}
return fullRight;
}

View File

@ -164,9 +164,10 @@ QSize Photo::countOptimalSize() {
if (_serviceWidth > 0) {
return { _serviceWidth, _serviceWidth };
}
const auto minWidth = qMax(
const auto minWidth = std::clamp(
_parent->minWidthForMedia(),
(_parent->hasBubble() ? st::historyPhotoBubbleMinWidth : st::minPhotoSize),
_parent->minWidthForMedia());
st::maxMediaSize);
const auto maxActualWidth = qMax(tw, minWidth);
maxWidth = qMax(maxActualWidth, th);
minHeight = qMax(th, st::minPhotoSize);
@ -206,9 +207,10 @@ QSize Photo::countCurrentSize(int newWidth) {
if (_pixw < 1) _pixw = 1;
if (_pixh < 1) _pixh = 1;
auto minWidth = qMax(
const auto minWidth = std::clamp(
_parent->minWidthForMedia(),
(_parent->hasBubble() ? st::historyPhotoBubbleMinWidth : st::minPhotoSize),
_parent->minWidthForMedia());
st::maxMediaSize);
newWidth = qMax(_pixw, minWidth);
auto newHeight = qMax(_pixh, st::minPhotoSize);
if (_parent->hasBubble() && !_caption.isEmpty()) {