Support media covers for bot descriptions.

For that replace custom handling of _botAbout by a fake message.
This commit is contained in:
John Preston 2023-03-03 20:52:21 +04:00
parent 59c66d1f49
commit 221b0d19c7
10 changed files with 325 additions and 205 deletions

View file

@ -293,6 +293,9 @@ enum class MessageFlag : uint64 {
OnlyEmojiAndSpaces = (1ULL << 34), OnlyEmojiAndSpaces = (1ULL << 34),
OnlyEmojiAndSpacesSet = (1ULL << 35), OnlyEmojiAndSpacesSet = (1ULL << 35),
// Fake message with bot cover and information.
FakeBotAbout = (1ULL << 36),
}; };
inline constexpr bool is_flag_type(MessageFlag) { return true; } inline constexpr bool is_flag_type(MessageFlag) { return true; }
using MessageFlags = base::flags<MessageFlag>; using MessageFlags = base::flags<MessageFlag>;

View file

@ -32,8 +32,7 @@ using UpdateFlag = Data::PeerUpdate::Flag;
} // namespace } // namespace
BotInfo::BotInfo() : text(st::msgMinWidth) { BotInfo::BotInfo() = default;
}
UserData::UserData(not_null<Data::Session*> owner, PeerId id) UserData::UserData(not_null<Data::Session*> owner, PeerId id)
: PeerData(owner, id) : PeerData(owner, id)
@ -188,10 +187,30 @@ void UserData::setBotInfo(const MTPBotInfo &info) {
return; return;
} }
QString desc = qs(d.vdescription().value_or_empty()); const auto description = qs(d.vdescription().value_or_empty());
if (botInfo->description != desc) { if (botInfo->description != description) {
botInfo->description = desc; botInfo->description = description;
botInfo->text = Ui::Text::String(st::msgMinWidth); ++botInfo->descriptionVersion;
}
if (const auto photo = d.vdescription_photo()) {
const auto parsed = owner().processPhoto(*photo);
if (botInfo->photo != parsed) {
botInfo->photo = parsed;
++botInfo->descriptionVersion;
}
} else if (botInfo->photo) {
botInfo->photo = nullptr;
++botInfo->descriptionVersion;
}
if (const auto document = d.vdescription_document()) {
const auto parsed = owner().processDocument(*document);
if (botInfo->document != parsed) {
botInfo->document = parsed;
++botInfo->descriptionVersion;
}
} else if (botInfo->document) {
botInfo->document = nullptr;
++botInfo->descriptionVersion;
} }
auto commands = d.vcommands() auto commands = d.vcommands()

View file

@ -24,9 +24,13 @@ struct BotInfo {
bool cantJoinGroups = false; bool cantJoinGroups = false;
bool supportsAttachMenu = false; bool supportsAttachMenu = false;
int version = 0; int version = 0;
QString description, inlinePlaceholder; int descriptionVersion = 0;
QString description;
QString inlinePlaceholder;
std::vector<Data::BotCommand> commands; std::vector<Data::BotCommand> commands;
Ui::Text::String text;
PhotoData *photo = nullptr;
DocumentData *document = nullptr;
QString botMenuButtonText; QString botMenuButtonText;
QString botMenuButtonUrl; QString botMenuButtonUrl;

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/crash_reports.h" #include "core/crash_reports.h"
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
#include "history/history.h" #include "history/history.h"
#include "history/admin_log/history_admin_log_item.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history_item_helpers.h" #include "history/history_item_helpers.h"
#include "history/view/media/history_view_media.h" #include "history/view/media/history_view_media.h"
@ -326,41 +327,100 @@ public:
}; };
class HistoryInner::BotAbout : public ClickHandlerHost { class HistoryInner::BotAbout final : public ClickHandlerHost {
public: public:
BotAbout(not_null<HistoryInner*> parent, not_null<BotInfo*> info); BotAbout(
not_null<History*> history,
not_null<HistoryView::ElementDelegate*> delegate);
// ClickHandlerHost interface [[nodiscard]] not_null<History*> history() const;
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; [[nodiscard]] HistoryView::Element *view() const;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; [[nodiscard]] HistoryItem *item() const;
not_null<BotInfo*> info; bool refresh();
int width = 0;
int top = 0;
int height = 0; int height = 0;
QRect rect;
private: private:
not_null<HistoryInner*> _parent; const not_null<History*> _history;
const not_null<HistoryView::ElementDelegate*> _delegate;
AdminLog::OwnedItem _item;
int _version = 0;
}; };
HistoryInner::BotAbout::BotAbout( HistoryInner::BotAbout::BotAbout(
not_null<HistoryInner*> parent, not_null<History*> history,
not_null<BotInfo*> info) not_null<HistoryView::ElementDelegate*> delegate)
: info(info) : _history(history)
, _parent(parent) { , _delegate(delegate) {
} }
void HistoryInner::BotAbout::clickHandlerActiveChanged( not_null<History*> HistoryInner::BotAbout::history() const {
const ClickHandlerPtr &p, return _history;
bool active) {
_parent->update(rect);
} }
void HistoryInner::BotAbout::clickHandlerPressedChanged( HistoryView::Element *HistoryInner::BotAbout::view() const {
const ClickHandlerPtr &p, return _item.get();
bool pressed) { }
_parent->update(rect);
HistoryItem *HistoryInner::BotAbout::item() const {
if (const auto element = view()) {
return element->data();
}
return nullptr;
}
bool HistoryInner::BotAbout::refresh() {
const auto bot = _history->peer->asUser();
const auto info = bot ? bot->botInfo.get() : nullptr;
if (!info) {
if (_item) {
_item = {};
return true;
}
_version = 0;
return false;
}
const auto version = info->descriptionVersion;
if (_version == version) {
return false;
}
_version = version;
const auto flags = MessageFlag::FakeBotAbout
| MessageFlag::FakeHistoryItem
| MessageFlag::Local;
const auto postAuthor = QString();
const auto date = TimeId(0);
const auto replyTo = MsgId(0);
const auto viaBotId = UserId(0);
const auto groupedId = uint64(0);
const auto textWithEntities = TextUtilities::ParseEntities(
info->description,
Ui::ItemTextBotNoMonoOptions().flags);
const auto make = [&](auto &&a, auto &&b, auto &&...other) {
return _history->makeMessage(
_history->nextNonHistoryEntryId(),
flags,
replyTo,
viaBotId,
date,
bot->id,
postAuthor,
std::forward<decltype(a)>(a),
std::forward<decltype(b)>(b),
HistoryMessageMarkupData(),
std::forward<decltype(other)>(other)...);
};
const auto item = info->document
? make(info->document, textWithEntities)
: info->photo
? make(info->photo, textWithEntities)
: make(textWithEntities, MTP_messageMediaEmpty(), groupedId);
_item = AdminLog::OwnedItem(_delegate, item);
return true;
} }
HistoryInner::HistoryInner( HistoryInner::HistoryInner(
@ -971,41 +1031,10 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
const auto historyDisplayedEmpty = _history->isDisplayedEmpty() const auto historyDisplayedEmpty = _history->isDisplayedEmpty()
&& (!_migrated || _migrated->isDisplayedEmpty()); && (!_migrated || _migrated->isDisplayedEmpty());
if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) { if (const auto view = _botAbout ? _botAbout->view() : nullptr) {
const auto st = context.st; if (clip.y() < _botAbout->top + _botAbout->height
const auto stm = &st->messageStyle(false, false); && clip.y() + clip.height() > _botAbout->top) {
if (clip.y() < _botAbout->rect.y() + _botAbout->rect.height() && clip.y() + clip.height() > _botAbout->rect.y()) { view->draw(p, context);
p.setTextPalette(stm->textPalette);
using Corner = Ui::BubbleCornerRounding;
const auto rounding = Ui::BubbleRounding{
Corner::Large,
Corner::Large,
Corner::Large,
Corner::Large,
};
Ui::PaintBubble(p, Ui::SimpleBubble{
.st = st,
.geometry = _botAbout->rect,
.pattern = context.bubblesPattern,
.patternViewport = context.viewport,
.outerWidth = width(),
.selected = false,
.outbg = false,
.rounding = rounding,
});
auto top = _botAbout->rect.top() + st::msgPadding.top();
if (!_history->peer->isRepliesChat()) {
p.setFont(st::msgNameFont);
p.setPen(st->dialogsNameFg());
p.drawText(_botAbout->rect.left() + st::msgPadding.left(), top + st::msgNameFont->ascent, tr::lng_bot_description(tr::now));
top += +st::msgNameFont->height + st::botDescSkip;
}
p.setPen(stm->historyTextFg);
_botAbout->info->text.draw(p, _botAbout->rect.left() + st::msgPadding.left(), top, _botAbout->width);
p.restoreTextPalette();
} }
} else if (historyDisplayedEmpty) { } else if (historyDisplayedEmpty) {
paintEmpty(p, context.st, width(), height()); paintEmpty(p, context.st, width(), height());
@ -2939,9 +2968,11 @@ void HistoryInner::recountHistoryGeometry() {
} }
const auto visibleHeight = _scroll->height(); const auto visibleHeight = _scroll->height();
int oldHistoryPaddingTop = qMax(visibleHeight - historyHeight() - st::historyPaddingBottom, 0); auto oldHistoryPaddingTop = qMax(
if (_botAbout && !_botAbout->info->text.isEmpty()) { visibleHeight - historyHeight() - st::historyPaddingBottom,
accumulate_max(oldHistoryPaddingTop, st::msgMargin.top() + st::msgMargin.bottom() + st::msgPadding.top() + st::msgPadding.bottom() + st::msgNameFont->height + st::botDescSkip + _botAbout->height); 0);
if (_botAbout) {
accumulate_max(oldHistoryPaddingTop, _botAbout->height);
} }
_history->resizeToWidth(_contentWidth); _history->resizeToWidth(_contentWidth);
@ -2968,39 +2999,20 @@ void HistoryInner::recountHistoryGeometry() {
} }
updateBotInfo(false); updateBotInfo(false);
if (_botAbout && !_botAbout->info->text.isEmpty()) { if (const auto view = _botAbout ? _botAbout->view() : nullptr) {
int32 tw = _scroll->width() - st::msgMargin.left() - st::msgMargin.right(); _botAbout->height = view->resizeGetHeight(_contentWidth);
if (tw > st::msgMaxWidth) tw = st::msgMaxWidth; _botAbout->top = qMin(
tw -= st::msgPadding.left() + st::msgPadding.right(); _historyPaddingTop - _botAbout->height,
const auto descriptionWidth = _history->peer->isRepliesChat() qMax(0, (_scroll->height() - _botAbout->height) / 2));
? 0
: st::msgNameFont->width(tr::lng_bot_description(tr::now));
int32 mw = qMax(_botAbout->info->text.maxWidth(), descriptionWidth);
if (tw > mw) tw = mw;
_botAbout->width = tw;
_botAbout->height = _botAbout->info->text.countHeight(_botAbout->width);
const auto descriptionHeight = _history->peer->isRepliesChat()
? 0
: (st::msgNameFont->height + st::botDescSkip);
int32 descH = st::msgMargin.top() + st::msgPadding.top() + descriptionHeight + _botAbout->height + st::msgPadding.bottom() + st::msgMargin.bottom();
int32 descMaxWidth = _scroll->width();
if (_isChatWide) {
descMaxWidth = qMin(descMaxWidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
}
int32 descAtX = (descMaxWidth - _botAbout->width) / 2 - st::msgPadding.left();
int32 descAtY = qMin(_historyPaddingTop - descH, qMax(0, (_scroll->height() - descH) / 2)) + st::msgMargin.top();
_botAbout->rect = QRect(descAtX, descAtY, _botAbout->width + st::msgPadding.left() + st::msgPadding.right(), descH - st::msgMargin.top() - st::msgMargin.bottom());
} else if (_botAbout) { } else if (_botAbout) {
_botAbout->width = _botAbout->height = 0; _botAbout->top = _botAbout->height = 0;
_botAbout->rect = QRect();
} }
int newHistoryPaddingTop = qMax(visibleHeight - historyHeight() - st::historyPaddingBottom, 0); auto newHistoryPaddingTop = qMax(
if (_botAbout && !_botAbout->info->text.isEmpty()) { visibleHeight - historyHeight() - st::historyPaddingBottom,
accumulate_max(newHistoryPaddingTop, st::msgMargin.top() + st::msgMargin.bottom() + st::msgPadding.top() + st::msgPadding.bottom() + st::msgNameFont->height + st::botDescSkip + _botAbout->height); 0);
if (_botAbout) {
accumulate_max(newHistoryPaddingTop, _botAbout->height);
} }
auto historyPaddingTopDelta = (newHistoryPaddingTop - oldHistoryPaddingTop); auto historyPaddingTopDelta = (newHistoryPaddingTop - oldHistoryPaddingTop);
@ -3014,48 +3026,15 @@ void HistoryInner::recountHistoryGeometry() {
} }
void HistoryInner::updateBotInfo(bool recount) { void HistoryInner::updateBotInfo(bool recount) {
int newh = 0; if (!_botAbout) {
if (_botAbout && !_botAbout->info->description.isEmpty()) { return;
if (_botAbout->info->text.isEmpty()) { } else if (_botAbout->refresh() && recount && _contentWidth > 0) {
_botAbout->info->text.setText( const auto view = _botAbout->view();
st::messageTextStyle, const auto now = view ? view->resizeGetHeight(_contentWidth) : 0;
_botAbout->info->description, if (_botAbout->height != now) {
Ui::ItemTextBotNoMonoOptions()); _botAbout->height = now;
if (recount) {
int32 tw = _scroll->width() - st::msgMargin.left() - st::msgMargin.right();
if (tw > st::msgMaxWidth) tw = st::msgMaxWidth;
tw -= st::msgPadding.left() + st::msgPadding.right();
const auto descriptionWidth = _history->peer->isRepliesChat()
? 0
: st::msgNameFont->width(tr::lng_bot_description(tr::now));
int32 mw = qMax(_botAbout->info->text.maxWidth(), descriptionWidth);
if (tw > mw) tw = mw;
_botAbout->width = tw;
newh = _botAbout->info->text.countHeight(_botAbout->width);
}
} else if (recount) {
newh = _botAbout->height;
}
}
if (recount && _botAbout) {
if (_botAbout->height != newh) {
_botAbout->height = newh;
updateSize(); updateSize();
} }
if (_botAbout->height > 0) {
const auto descriptionHeight = _history->peer->isRepliesChat()
? 0
: (st::msgNameFont->height + st::botDescSkip);
int32 descH = st::msgMargin.top() + st::msgPadding.top() + descriptionHeight + _botAbout->height + st::msgPadding.bottom() + st::msgMargin.bottom();
int32 descAtX = (_scroll->width() - _botAbout->width) / 2 - st::msgPadding.left();
int32 descAtY = qMin(_historyPaddingTop - descH, (_scroll->height() - descH) / 2) + st::msgMargin.top();
_botAbout->rect = QRect(descAtX, descAtY, _botAbout->width + st::msgPadding.left() + st::msgPadding.right(), descH - st::msgMargin.top() - st::msgMargin.bottom());
} else {
_botAbout->width = 0;
_botAbout->rect = QRect();
}
} }
} }
@ -3200,24 +3179,15 @@ void HistoryInner::changeItemsRevealHeight(int revealHeight) {
void HistoryInner::updateSize() { void HistoryInner::updateSize() {
const auto visibleHeight = _scroll->height(); const auto visibleHeight = _scroll->height();
const auto itemsHeight = historyHeight() - _revealHeight; const auto itemsHeight = historyHeight() - _revealHeight;
int newHistoryPaddingTop = qMax(visibleHeight - itemsHeight - st::historyPaddingBottom, 0); auto newHistoryPaddingTop = qMax(visibleHeight - itemsHeight - st::historyPaddingBottom, 0);
if (_botAbout && !_botAbout->info->text.isEmpty()) { if (_botAbout) {
accumulate_max(newHistoryPaddingTop, st::msgMargin.top() + st::msgMargin.bottom() + st::msgPadding.top() + st::msgPadding.bottom() + st::msgNameFont->height + st::botDescSkip + _botAbout->height); accumulate_max(newHistoryPaddingTop, _botAbout->height);
} }
if (_botAbout && _botAbout->height > 0) { if (_botAbout && _botAbout->height > 0) {
const auto descriptionHeight = _history->peer->isRepliesChat() _botAbout->top = qMin(
? 0 newHistoryPaddingTop - _botAbout->height,
: (st::msgNameFont->height + st::botDescSkip); qMax(0, (_scroll->height() - _botAbout->height) / 2));
int32 descH = st::msgMargin.top() + st::msgPadding.top() + descriptionHeight + _botAbout->height + st::msgPadding.bottom() + st::msgMargin.bottom();
int32 descMaxWidth = _scroll->width();
if (_isChatWide) {
descMaxWidth = qMin(descMaxWidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
}
int32 descAtX = (descMaxWidth - _botAbout->width) / 2 - st::msgPadding.left();
int32 descAtY = qMin(newHistoryPaddingTop - descH, qMax(0, (_scroll->height() - descH) / 2)) + st::msgMargin.top();
_botAbout->rect = QRect(descAtX, descAtY, _botAbout->width + st::msgPadding.left() + st::msgPadding.right(), descH - st::msgMargin.top() - st::msgMargin.bottom());
} }
if (_historyPaddingTop != newHistoryPaddingTop) { if (_historyPaddingTop != newHistoryPaddingTop) {
@ -3261,6 +3231,7 @@ void HistoryInner::leaveEventHook(QEvent *e) {
} }
HistoryInner::~HistoryInner() { HistoryInner::~HistoryInner() {
_botAbout = nullptr;
for (const auto &item : _animatedStickersPlayed) { for (const auto &item : _animatedStickersPlayed) {
if (const auto view = item->mainView()) { if (const auto view = item->mainView()) {
if (const auto media = view->media()) { if (const auto media = view->media()) {
@ -3632,12 +3603,15 @@ void HistoryInner::mouseActionUpdate() {
dragState = reactionState; dragState = reactionState;
lnkhost = reactionView; lnkhost = reactionView;
} else if (point.y() < _historyPaddingTop) { } else if (point.y() < _historyPaddingTop) {
if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) { if (const auto view = _botAbout ? _botAbout->view() : nullptr) {
dragState = TextState(nullptr, _botAbout->info->text.getState( StateRequest request;
point - _botAbout->rect.topLeft() - QPoint(st::msgPadding.left(), st::msgPadding.top() + st::botDescSkip + st::msgNameFont->height), if (base::IsAltPressed()) {
_botAbout->width)); request.flags &= ~Ui::Text::StateRequest::Flag::LookupLink;
}
const auto relative = point - QPoint(0, _botAbout->top);
dragState = view->textState(relative, request);
_dragStateItem = session().data().message(dragState.itemId); _dragStateItem = session().data().message(dragState.itemId);
lnkhost = _botAbout.get(); lnkhost = view;
} }
} else if (item) { } else if (item) {
if (item != _mouseActionItem || (m - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) { if (item != _mouseActionItem || (m - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) {
@ -3947,16 +3921,22 @@ void HistoryInner::setCanHaveFromUserpicsSponsored(bool value) {
int HistoryInner::itemTop(const HistoryItem *item) const { int HistoryInner::itemTop(const HistoryItem *item) const {
if (!item) { if (!item) {
return -2; return -2;
} else if (_botAbout && item == _botAbout->item()) {
return _botAbout->top;
} }
return itemTop(item->mainView()); return itemTop(item->mainView());
} }
int HistoryInner::itemTop(const Element *view) const { int HistoryInner::itemTop(const Element *view) const {
if (!view || view->data()->mainView() != view) { if (!view) {
return -1;
} else if (_botAbout && view == _botAbout->view()) {
return _botAbout->top;
} else if (view->data()->mainView() != view) {
return -1; return -1;
} }
auto top = (view->history() == _history) const auto top = (view->history() == _history)
? historyTop() ? historyTop()
: (view->history() == _migrated : (view->history() == _migrated
? migratedTop() ? migratedTop()
@ -3990,21 +3970,17 @@ auto HistoryInner::findViewForPinnedTracking(int top) const
} }
void HistoryInner::notifyIsBotChanged() { void HistoryInner::notifyIsBotChanged() {
const auto newinfo = _peer->isUser() if (const auto user = _peer->asUser()) {
? _peer->asUser()->botInfo.get() if (const auto info = user->botInfo.get()) {
: nullptr; if (!_botAbout) {
if ((!newinfo && !_botAbout) _botAbout = std::make_unique<BotAbout>(
|| (newinfo && _botAbout && _botAbout->info == newinfo)) { _history,
return; _history->delegateMixin()->delegate());
} }
if (!info->inited) {
if (newinfo) { session().api().requestFullPeer(_peer);
_botAbout = std::make_unique<BotAbout>(this, newinfo); }
if (newinfo && !newinfo->inited) {
session().api().requestFullPeer(_peer);
} }
} else {
_botAbout = nullptr;
} }
} }

View file

@ -306,6 +306,9 @@ public:
[[nodiscard]] bool isLocal() const { [[nodiscard]] bool isLocal() const {
return _flags & MessageFlag::Local; return _flags & MessageFlag::Local;
} }
[[nodiscard]] bool isFakeBotAbout() const {
return _flags & MessageFlag::FakeBotAbout;
}
[[nodiscard]] bool isRegular() const; [[nodiscard]] bool isRegular() const;
[[nodiscard]] bool isUploading() const; [[nodiscard]] bool isUploading() const;
void sendFailed(); void sendFailed();

View file

@ -354,6 +354,20 @@ void DateBadge::paint(
ServiceMessagePainter::PaintDate(p, st, text, width, y, w, chatWide); ServiceMessagePainter::PaintDate(p, st, text, width, y, w, chatWide);
} }
void FakeBotAboutTop::init() {
if (!text.isEmpty()) {
return;
}
text.setText(
st::msgNameStyle,
tr::lng_bot_description(tr::now),
Ui::NameTextOptions());
maxWidth = st::msgPadding.left()
+ text.maxWidth()
+ st::msgPadding.right();
height = st::msgNameStyle.font->height + st::botDescSkip;
}
Element::Element( Element::Element(
not_null<ElementDelegate*> delegate, not_null<ElementDelegate*> delegate,
not_null<HistoryItem*> data, not_null<HistoryItem*> data,
@ -376,6 +390,9 @@ Element::Element(
if (_context == Context::History) { if (_context == Context::History) {
history()->setHasPendingResizedItems(); history()->setHasPendingResizedItems();
} }
if (data->isFakeBotAbout() && !data->history()->peer->isRepliesChat()) {
AddComponents(FakeBotAboutTop::Bit());
}
} }
not_null<ElementDelegate*> Element::delegate() const { not_null<ElementDelegate*> Element::delegate() const {

View file

@ -230,6 +230,14 @@ struct DateBadge : public RuntimeComponent<DateBadge, Element> {
}; };
struct FakeBotAboutTop : public RuntimeComponent<FakeBotAboutTop, Element> {
void init();
Ui::Text::String text;
int maxWidth = 0;
int height = 0;
};
struct TopicButton { struct TopicButton {
std::unique_ptr<Ui::RippleAnimation> ripple; std::unique_ptr<Ui::RippleAnimation> ripple;
ClickHandlerPtr link; ClickHandlerPtr link;

View file

@ -530,6 +530,12 @@ QSize Message::performCountOptimalSize() {
refreshInfoSkipBlock(); refreshInfoSkipBlock();
const auto media = this->media(); const auto media = this->media();
const auto botTop = item->isFakeBotAbout()
? Get<FakeBotAboutTop>()
: nullptr;
if (botTop) {
botTop->init();
}
auto maxWidth = 0; auto maxWidth = 0;
auto minHeight = 0; auto minHeight = 0;
@ -560,13 +566,14 @@ QSize Message::performCountOptimalSize() {
} }
// Entry page is always a bubble bottom. // Entry page is always a bubble bottom.
const auto withVisibleText = hasVisibleText();
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());
maxWidth = plainMaxWidth(); maxWidth = plainMaxWidth();
if (context() == Context::Replies && item->isDiscussionPost()) { if (context() == Context::Replies && item->isDiscussionPost()) {
maxWidth = std::max(maxWidth, st::msgMaxWidth); maxWidth = std::max(maxWidth, st::msgMaxWidth);
} }
minHeight = hasVisibleText() ? text().minHeight() : 0; minHeight = withVisibleText ? text().minHeight() : 0;
if (reactionsInBubble) { if (reactionsInBubble) {
const auto reactionsMaxWidth = st::msgPadding.left() const auto reactionsMaxWidth = st::msgPadding.left()
+ _reactions->maxWidth() + _reactions->maxWidth()
@ -604,9 +611,14 @@ QSize Message::performCountOptimalSize() {
const auto innerWidth = maxWidth const auto innerWidth = maxWidth
- st::msgPadding.left() - st::msgPadding.left()
- st::msgPadding.right(); - st::msgPadding.right();
if (hasVisibleText() && maxWidth < plainMaxWidth()) { if (withVisibleText) {
minHeight -= text().minHeight(); if (botTop) {
minHeight += text().countHeight(innerWidth); minHeight += botTop->height;
}
if (maxWidth < plainMaxWidth()) {
minHeight -= text().minHeight();
minHeight += text().countHeight(innerWidth);
}
} }
if (reactionsInBubble) { if (reactionsInBubble) {
minHeight -= _reactions->minHeight(); minHeight -= _reactions->minHeight();
@ -678,6 +690,10 @@ QSize Message::performCountOptimalSize() {
accumulate_max(maxWidth, entry->maxWidth()); accumulate_max(maxWidth, entry->maxWidth());
minHeight += entry->minHeight(); minHeight += entry->minHeight();
} }
if (withVisibleText && botTop) {
accumulate_max(maxWidth, botTop->maxWidth);
minHeight += botTop->height;
}
} }
accumulate_max(maxWidth, minWidthForMedia()); accumulate_max(maxWidth, minWidthForMedia());
} else if (media) { } else if (media) {
@ -1467,6 +1483,15 @@ void Message::paintText(
p.setPen(stm->historyTextFg); p.setPen(stm->historyTextFg);
p.setFont(st::msgFont); p.setFont(st::msgFont);
prepareCustomEmojiPaint(p, context, text()); prepareCustomEmojiPaint(p, context, text());
if (const auto botTop = Get<FakeBotAboutTop>()) {
botTop->text.drawLeftElided(
p,
trect.x(),
trect.y(),
trect.width(),
width());
trect.setY(trect.y() + botTop->height);
}
text().draw(p, { text().draw(p, {
.position = trect.topLeft(), .position = trect.topLeft(),
.availableWidth = trect.width(), .availableWidth = trect.width(),
@ -2281,6 +2306,8 @@ bool Message::getStateText(
StateRequest request) const { StateRequest request) const {
if (!hasVisibleText()) { if (!hasVisibleText()) {
return false; return false;
} else if (const auto botTop = Get<FakeBotAboutTop>()) {
trect.setY(trect.y() + botTop->height);
} }
const auto item = data(); const auto item = data();
if (base::in_range(point.y(), trect.y(), trect.y() + trect.height())) { if (base::in_range(point.y(), trect.y(), trect.y() + trect.height())) {
@ -2886,7 +2913,7 @@ bool Message::drawBubble() const {
const auto item = data(); const auto item = data();
if (isHidden()) { if (isHidden()) {
return false; return false;
} else if (logEntryOriginal()) { } else if (logEntryOriginal() || item->isFakeBotAbout()) {
return true; return true;
} }
const auto media = this->media(); const auto media = this->media();
@ -3376,8 +3403,9 @@ QRect Message::innerGeometry() const {
} }
QRect Message::countGeometry() const { QRect Message::countGeometry() const {
const auto commentsRoot = (context() == Context::Replies) const auto item = data();
&& data()->isDiscussionPost(); const auto centeredView = item->isFakeBotAbout()
|| (context() == Context::Replies && item->isDiscussionPost());
const auto media = this->media(); const auto media = this->media();
const auto mediaWidth = (media && media->isDisplayed()) const auto mediaWidth = (media && media->isDisplayed())
? media->width() ? media->width()
@ -3385,7 +3413,7 @@ QRect Message::countGeometry() const {
const auto outbg = hasOutLayout(); const auto outbg = hasOutLayout();
const auto availableWidth = width() const auto availableWidth = width()
- st::msgMargin.left() - st::msgMargin.left()
- (commentsRoot ? st::msgMargin.left() : st::msgMargin.right()); - (centeredView ? st::msgMargin.left() : st::msgMargin.right());
auto contentLeft = (outbg && !delegate()->elementIsChatWide()) auto contentLeft = (outbg && !delegate()->elementIsChatWide())
? st::msgMargin.right() ? st::msgMargin.right()
: st::msgMargin.left(); : st::msgMargin.left();
@ -3412,10 +3440,10 @@ QRect Message::countGeometry() const {
if (contentWidth < availableWidth && !delegate()->elementIsChatWide()) { if (contentWidth < availableWidth && !delegate()->elementIsChatWide()) {
if (outbg) { if (outbg) {
contentLeft += availableWidth - contentWidth; contentLeft += availableWidth - contentWidth;
} else if (commentsRoot) { } else if (centeredView) {
contentLeft += (availableWidth - contentWidth) / 2; contentLeft += (availableWidth - contentWidth) / 2;
} }
} else if (contentWidth < availableWidth && commentsRoot) { } else if (contentWidth < availableWidth && centeredView) {
contentLeft += std::max( contentLeft += std::max(
((st::msgMaxWidth + 2 * st::msgPhotoSkip) - contentWidth) / 2, ((st::msgMaxWidth + 2 * st::msgPhotoSkip) - contentWidth) / 2,
0); 0);
@ -3433,11 +3461,13 @@ Ui::BubbleRounding Message::countMessageRounding() const {
const auto smallTop = isBubbleAttachedToPrevious(); const auto smallTop = isBubbleAttachedToPrevious();
const auto smallBottom = isBubbleAttachedToNext(); const auto smallBottom = isBubbleAttachedToNext();
const auto media = smallBottom ? nullptr : this->media(); const auto media = smallBottom ? nullptr : this->media();
const auto keyboard = data()->inlineReplyKeyboard(); const auto item = data();
const auto keyboard = item->inlineReplyKeyboard();
const auto skipTail = smallBottom const auto skipTail = smallBottom
|| (media && media->skipBubbleTail()) || (media && media->skipBubbleTail())
|| (keyboard != nullptr) || (keyboard != nullptr)
|| (context() == Context::Replies && data()->isDiscussionPost()); || item->isFakeBotAbout()
|| (context() == Context::Replies && item->isDiscussionPost());
const auto right = !delegate()->elementIsChatWide() && hasOutLayout(); const auto right = !delegate()->elementIsChatWide() && hasOutLayout();
using Corner = Ui::BubbleCornerRounding; using Corner = Ui::BubbleCornerRounding;
return Ui::BubbleRounding{ return Ui::BubbleRounding{
@ -3480,16 +3510,19 @@ int Message::resizeContentGetHeight(int newWidth) {
auto newHeight = minHeight(); auto newHeight = minHeight();
const auto item = data(); const auto item = data();
const auto botTop = item->isFakeBotAbout()
? Get<FakeBotAboutTop>()
: nullptr;
const auto media = this->media(); const auto media = this->media();
const auto mediaDisplayed = media ? media->isDisplayed() : false; const auto mediaDisplayed = media ? media->isDisplayed() : false;
const auto bubble = drawBubble(); const auto bubble = drawBubble();
// This code duplicates countGeometry() but also resizes media. // This code duplicates countGeometry() but also resizes media.
const auto commentsRoot = (context() == Context::Replies) const auto centeredView = item->isFakeBotAbout()
&& data()->isDiscussionPost(); || (context() == Context::Replies && item->isDiscussionPost());
auto contentWidth = newWidth auto contentWidth = newWidth
- st::msgMargin.left() - st::msgMargin.left()
- (commentsRoot ? st::msgMargin.left() : st::msgMargin.right()); - (centeredView ? st::msgMargin.left() : st::msgMargin.right());
if (hasFromPhoto()) { if (hasFromPhoto()) {
if (const auto size = rightActionSize()) { if (const auto size = rightActionSize()) {
contentWidth -= size->width() + (st::msgPhotoSkip - st::historyFastShareSize); contentWidth -= size->width() + (st::msgPhotoSkip - st::historyFastShareSize);
@ -3540,7 +3573,14 @@ int Message::resizeContentGetHeight(int newWidth) {
entry->resizeGetHeight(contentWidth); entry->resizeGetHeight(contentWidth);
} }
} else { } else {
newHeight = hasVisibleText() ? textHeightFor(textWidth) : 0; const auto withVisibleText = hasVisibleText();
newHeight = 0;
if (withVisibleText) {
if (botTop) {
newHeight += botTop->height;
}
newHeight += textHeightFor(textWidth);
}
if (!mediaOnBottom && (!_viewButton || !reactionsInBubble)) { if (!mediaOnBottom && (!_viewButton || !reactionsInBubble)) {
newHeight += st::msgPadding.bottom(); newHeight += st::msgPadding.bottom();
if (mediaDisplayed) { if (mediaDisplayed) {

View file

@ -199,6 +199,10 @@ QSize Gif::countOptimalSize() {
minHeight = adjustHeightForLessCrop( minHeight = adjustHeightForLessCrop(
scaled, scaled,
{ maxWidth, minHeight }); { maxWidth, minHeight });
if (const auto botTop = _parent->Get<FakeBotAboutTop>()) {
accumulate_max(maxWidth, botTop->maxWidth);
minHeight += botTop->height;
}
minHeight += st::mediaCaptionSkip + _caption.minHeight(); minHeight += st::mediaCaptionSkip + _caption.minHeight();
if (isBubbleBottom()) { if (isBubbleBottom()) {
minHeight += st::msgPadding.bottom(); minHeight += st::msgPadding.bottom();
@ -236,11 +240,14 @@ QSize Gif::countCurrentSize(int newWidth) {
if (_parent->hasBubble()) { if (_parent->hasBubble()) {
accumulate_max(newWidth, _parent->minWidthForMedia()); accumulate_max(newWidth, _parent->minWidthForMedia());
if (!_caption.isEmpty()) { if (!_caption.isEmpty()) {
const auto maxWithCaption = qMin( auto captionMaxWidth = st::msgPadding.left()
st::msgMaxWidth, + _caption.maxWidth()
(st::msgPadding.left() + st::msgPadding.right();
+ _caption.maxWidth() const auto botTop = _parent->Get<FakeBotAboutTop>();
+ st::msgPadding.right())); if (botTop) {
accumulate_max(captionMaxWidth, botTop->maxWidth);
}
const auto maxWithCaption = qMin(st::msgMaxWidth, captionMaxWidth);
newWidth = qMin(qMax(newWidth, maxWithCaption), thumbMaxWidth); newWidth = qMin(qMax(newWidth, maxWithCaption), thumbMaxWidth);
newHeight = adjustHeightForLessCrop( newHeight = adjustHeightForLessCrop(
scaled, scaled,
@ -248,6 +255,9 @@ QSize Gif::countCurrentSize(int newWidth) {
const auto captionw = newWidth const auto captionw = newWidth
- st::msgPadding.left() - st::msgPadding.left()
- st::msgPadding.right(); - st::msgPadding.right();
if (botTop) {
newHeight += botTop->height;
}
newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
if (isBubbleBottom()) { if (isBubbleBottom()) {
newHeight += st::msgPadding.bottom(); newHeight += st::msgPadding.bottom();
@ -349,12 +359,16 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
const auto outbg = context.outbg; const auto outbg = context.outbg;
const auto inWebPage = (_parent->media() != this); const auto inWebPage = (_parent->media() != this);
const auto isRound = _data->isVideoMessage(); const auto isRound = _data->isVideoMessage();
const auto botTop = _parent->Get<FakeBotAboutTop>();
const auto rounding = inWebPage const auto rounding = inWebPage
? std::optional<Ui::BubbleRounding>() ? std::optional<Ui::BubbleRounding>()
: adjustedBubbleRoundingWithCaption(_caption); : adjustedBubbleRoundingWithCaption(_caption);
if (bubble) { if (bubble) {
if (!_caption.isEmpty()) { if (!_caption.isEmpty()) {
if (botTop) {
painth -= botTop->height;
}
painth -= st::mediaCaptionSkip + _caption.countHeight(captionw); painth -= st::mediaCaptionSkip + _caption.countHeight(captionw);
if (isBubbleBottom()) { if (isBubbleBottom()) {
painth -= st::msgPadding.bottom(); painth -= st::msgPadding.bottom();
@ -674,10 +688,18 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
if (!unwrapped && !_caption.isEmpty()) { if (!unwrapped && !_caption.isEmpty()) {
p.setPen(stm->historyTextFg); p.setPen(stm->historyTextFg);
_parent->prepareCustomEmojiPaint(p, context, _caption); _parent->prepareCustomEmojiPaint(p, context, _caption);
_caption.draw(p, { auto top = painty + painth + st::mediaCaptionSkip;
.position = QPoint( if (botTop) {
botTop->text.drawLeftElided(
p,
st::msgPadding.left(), st::msgPadding.left(),
painty + painth + st::mediaCaptionSkip), top,
captionw,
_parent->width());
top += botTop->height;
}
_caption.draw(p, {
.position = QPoint(st::msgPadding.left(), top),
.availableWidth = captionw, .availableWidth = captionw,
.palette = &stm->textPalette, .palette = &stm->textPalette,
.spoiler = Ui::Text::DefaultSpoilerCache(), .spoiler = Ui::Text::DefaultSpoilerCache(),
@ -956,6 +978,9 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
request.forText())); request.forText()));
return result; return result;
} }
if (const auto botTop = _parent->Get<FakeBotAboutTop>()) {
painth -= botTop->height;
}
painth -= st::mediaCaptionSkip; painth -= st::mediaCaptionSkip;
} }
const auto outbg = _parent->hasOutLayout(); const auto outbg = _parent->hasOutLayout();

View file

@ -187,6 +187,10 @@ QSize Photo::countOptimalSize() {
minHeight = adjustHeightForLessCrop( minHeight = adjustHeightForLessCrop(
dimensions, dimensions,
{ maxWidth, minHeight }); { maxWidth, minHeight });
if (const auto botTop = _parent->Get<FakeBotAboutTop>()) {
accumulate_max(maxWidth, botTop->maxWidth);
minHeight += botTop->height;
}
minHeight += st::mediaCaptionSkip + _caption.minHeight(); minHeight += st::mediaCaptionSkip + _caption.minHeight();
if (isBubbleBottom()) { if (isBubbleBottom()) {
minHeight += st::msgPadding.bottom(); minHeight += st::msgPadding.bottom();
@ -214,11 +218,14 @@ QSize Photo::countCurrentSize(int newWidth) {
newWidth = qMax(pix.width(), minWidth); newWidth = qMax(pix.width(), minWidth);
auto newHeight = qMax(pix.height(), st::minPhotoSize); auto newHeight = qMax(pix.height(), st::minPhotoSize);
if (_parent->hasBubble() && !_caption.isEmpty()) { if (_parent->hasBubble() && !_caption.isEmpty()) {
const auto maxWithCaption = qMin( auto captionMaxWidth = st::msgPadding.left()
st::msgMaxWidth, + _caption.maxWidth()
(st::msgPadding.left() + st::msgPadding.right();
+ _caption.maxWidth() const auto botTop = _parent->Get<FakeBotAboutTop>();
+ st::msgPadding.right())); if (botTop) {
accumulate_max(captionMaxWidth, botTop->maxWidth);
}
const auto maxWithCaption = qMin(st::msgMaxWidth, captionMaxWidth);
newWidth = qMin(qMax(newWidth, maxWithCaption), thumbMaxWidth); newWidth = qMin(qMax(newWidth, maxWithCaption), thumbMaxWidth);
newHeight = adjustHeightForLessCrop( newHeight = adjustHeightForLessCrop(
dimensions, dimensions,
@ -226,6 +233,9 @@ QSize Photo::countCurrentSize(int newWidth) {
const auto captionw = newWidth const auto captionw = newWidth
- st::msgPadding.left() - st::msgPadding.left()
- st::msgPadding.right(); - st::msgPadding.right();
if (botTop) {
newHeight += botTop->height;
}
newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
if (isBubbleBottom()) { if (isBubbleBottom()) {
newHeight += st::msgPadding.bottom(); newHeight += st::msgPadding.bottom();
@ -268,6 +278,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
} }
} }
const auto radial = isRadialAnimation(); const auto radial = isRadialAnimation();
const auto botTop = _parent->Get<FakeBotAboutTop>();
auto rthumb = style::rtlrect(paintx, painty, paintw, painth, width()); auto rthumb = style::rtlrect(paintx, painty, paintw, painth, width());
if (_serviceWidth > 0) { if (_serviceWidth > 0) {
@ -279,6 +290,9 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
if (bubble) { if (bubble) {
if (!_caption.isEmpty()) { if (!_caption.isEmpty()) {
painth -= st::mediaCaptionSkip + _caption.countHeight(captionw); painth -= st::mediaCaptionSkip + _caption.countHeight(captionw);
if (botTop) {
painth -= botTop->height;
}
if (isBubbleBottom()) { if (isBubbleBottom()) {
painth -= st::msgPadding.bottom(); painth -= st::msgPadding.bottom();
} }
@ -348,10 +362,18 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
if (!_caption.isEmpty()) { if (!_caption.isEmpty()) {
p.setPen(stm->historyTextFg); p.setPen(stm->historyTextFg);
_parent->prepareCustomEmojiPaint(p, context, _caption); _parent->prepareCustomEmojiPaint(p, context, _caption);
_caption.draw(p, { auto top = painty + painth + st::mediaCaptionSkip;
.position = QPoint( if (botTop) {
botTop->text.drawLeftElided(
p,
st::msgPadding.left(), st::msgPadding.left(),
painty + painth + st::mediaCaptionSkip), top,
captionw,
_parent->width());
top += botTop->height;
}
_caption.draw(p, {
.position = QPoint(st::msgPadding.left(), top),
.availableWidth = captionw, .availableWidth = captionw,
.palette = &stm->textPalette, .palette = &stm->textPalette,
.spoiler = Ui::Text::DefaultSpoilerCache(), .spoiler = Ui::Text::DefaultSpoilerCache(),
@ -592,6 +614,9 @@ TextState Photo::textState(QPoint point, StateRequest request) const {
request.forText())); request.forText()));
return result; return result;
} }
if (const auto botTop = _parent->Get<FakeBotAboutTop>()) {
painth -= botTop->height;
}
painth -= st::mediaCaptionSkip; painth -= st::mediaCaptionSkip;
} }
if (QRect(paintx, painty, paintw, painth).contains(point)) { if (QRect(paintx, painty, paintw, painth).contains(point)) {