From 221b0d19c7ff0f379b542df1858a3562fdf17581 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 3 Mar 2023 20:52:21 +0400 Subject: [PATCH] Support media covers for bot descriptions. For that replace custom handling of _botAbout by a fake message. --- Telegram/SourceFiles/data/data_types.h | 3 + Telegram/SourceFiles/data/data_user.cpp | 31 +- Telegram/SourceFiles/data/data_user.h | 8 +- .../history/history_inner_widget.cpp | 306 ++++++++---------- Telegram/SourceFiles/history/history_item.h | 3 + .../history/view/history_view_element.cpp | 17 + .../history/view/history_view_element.h | 8 + .../history/view/history_view_message.cpp | 72 ++++- .../history/view/media/history_view_gif.cpp | 41 ++- .../history/view/media/history_view_photo.cpp | 41 ++- 10 files changed, 325 insertions(+), 205 deletions(-) diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 375e9da07..aa3ade241 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -293,6 +293,9 @@ enum class MessageFlag : uint64 { OnlyEmojiAndSpaces = (1ULL << 34), OnlyEmojiAndSpacesSet = (1ULL << 35), + + // Fake message with bot cover and information. + FakeBotAbout = (1ULL << 36), }; inline constexpr bool is_flag_type(MessageFlag) { return true; } using MessageFlags = base::flags<MessageFlag>; diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index ae0fd3c61..f474f701e 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -32,8 +32,7 @@ using UpdateFlag = Data::PeerUpdate::Flag; } // namespace -BotInfo::BotInfo() : text(st::msgMinWidth) { -} +BotInfo::BotInfo() = default; UserData::UserData(not_null<Data::Session*> owner, PeerId id) : PeerData(owner, id) @@ -188,10 +187,30 @@ void UserData::setBotInfo(const MTPBotInfo &info) { return; } - QString desc = qs(d.vdescription().value_or_empty()); - if (botInfo->description != desc) { - botInfo->description = desc; - botInfo->text = Ui::Text::String(st::msgMinWidth); + const auto description = qs(d.vdescription().value_or_empty()); + if (botInfo->description != description) { + botInfo->description = description; + ++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() diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index a51001095..e0355d8e9 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -24,9 +24,13 @@ struct BotInfo { bool cantJoinGroups = false; bool supportsAttachMenu = false; int version = 0; - QString description, inlinePlaceholder; + int descriptionVersion = 0; + QString description; + QString inlinePlaceholder; std::vector<Data::BotCommand> commands; - Ui::Text::String text; + + PhotoData *photo = nullptr; + DocumentData *document = nullptr; QString botMenuButtonText; QString botMenuButtonUrl; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index c8b8416c1..fbd913acc 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/crash_reports.h" #include "core/click_handler_types.h" #include "history/history.h" +#include "history/admin_log/history_admin_log_item.h" #include "history/history_item.h" #include "history/history_item_helpers.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: - BotAbout(not_null<HistoryInner*> parent, not_null<BotInfo*> info); + BotAbout( + not_null<History*> history, + not_null<HistoryView::ElementDelegate*> delegate); - // ClickHandlerHost interface - void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; - void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; + [[nodiscard]] not_null<History*> history() const; + [[nodiscard]] HistoryView::Element *view() const; + [[nodiscard]] HistoryItem *item() const; - not_null<BotInfo*> info; - int width = 0; + bool refresh(); + + int top = 0; int height = 0; - QRect rect; 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( - not_null<HistoryInner*> parent, - not_null<BotInfo*> info) -: info(info) -, _parent(parent) { + not_null<History*> history, + not_null<HistoryView::ElementDelegate*> delegate) +: _history(history) +, _delegate(delegate) { } -void HistoryInner::BotAbout::clickHandlerActiveChanged( - const ClickHandlerPtr &p, - bool active) { - _parent->update(rect); +not_null<History*> HistoryInner::BotAbout::history() const { + return _history; } -void HistoryInner::BotAbout::clickHandlerPressedChanged( - const ClickHandlerPtr &p, - bool pressed) { - _parent->update(rect); +HistoryView::Element *HistoryInner::BotAbout::view() const { + return _item.get(); +} + +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( @@ -971,41 +1031,10 @@ void HistoryInner::paintEvent(QPaintEvent *e) { const auto historyDisplayedEmpty = _history->isDisplayedEmpty() && (!_migrated || _migrated->isDisplayedEmpty()); - if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) { - const auto st = context.st; - const auto stm = &st->messageStyle(false, false); - if (clip.y() < _botAbout->rect.y() + _botAbout->rect.height() && clip.y() + clip.height() > _botAbout->rect.y()) { - 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(); + if (const auto view = _botAbout ? _botAbout->view() : nullptr) { + if (clip.y() < _botAbout->top + _botAbout->height + && clip.y() + clip.height() > _botAbout->top) { + view->draw(p, context); } } else if (historyDisplayedEmpty) { paintEmpty(p, context.st, width(), height()); @@ -2939,9 +2968,11 @@ void HistoryInner::recountHistoryGeometry() { } const auto visibleHeight = _scroll->height(); - int oldHistoryPaddingTop = qMax(visibleHeight - historyHeight() - st::historyPaddingBottom, 0); - if (_botAbout && !_botAbout->info->text.isEmpty()) { - accumulate_max(oldHistoryPaddingTop, st::msgMargin.top() + st::msgMargin.bottom() + st::msgPadding.top() + st::msgPadding.bottom() + st::msgNameFont->height + st::botDescSkip + _botAbout->height); + auto oldHistoryPaddingTop = qMax( + visibleHeight - historyHeight() - st::historyPaddingBottom, + 0); + if (_botAbout) { + accumulate_max(oldHistoryPaddingTop, _botAbout->height); } _history->resizeToWidth(_contentWidth); @@ -2968,39 +2999,20 @@ void HistoryInner::recountHistoryGeometry() { } updateBotInfo(false); - if (_botAbout && !_botAbout->info->text.isEmpty()) { - 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; - _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()); + if (const auto view = _botAbout ? _botAbout->view() : nullptr) { + _botAbout->height = view->resizeGetHeight(_contentWidth); + _botAbout->top = qMin( + _historyPaddingTop - _botAbout->height, + qMax(0, (_scroll->height() - _botAbout->height) / 2)); } else if (_botAbout) { - _botAbout->width = _botAbout->height = 0; - _botAbout->rect = QRect(); + _botAbout->top = _botAbout->height = 0; } - int newHistoryPaddingTop = qMax(visibleHeight - historyHeight() - st::historyPaddingBottom, 0); - if (_botAbout && !_botAbout->info->text.isEmpty()) { - accumulate_max(newHistoryPaddingTop, st::msgMargin.top() + st::msgMargin.bottom() + st::msgPadding.top() + st::msgPadding.bottom() + st::msgNameFont->height + st::botDescSkip + _botAbout->height); + auto newHistoryPaddingTop = qMax( + visibleHeight - historyHeight() - st::historyPaddingBottom, + 0); + if (_botAbout) { + accumulate_max(newHistoryPaddingTop, _botAbout->height); } auto historyPaddingTopDelta = (newHistoryPaddingTop - oldHistoryPaddingTop); @@ -3014,48 +3026,15 @@ void HistoryInner::recountHistoryGeometry() { } void HistoryInner::updateBotInfo(bool recount) { - int newh = 0; - if (_botAbout && !_botAbout->info->description.isEmpty()) { - if (_botAbout->info->text.isEmpty()) { - _botAbout->info->text.setText( - st::messageTextStyle, - _botAbout->info->description, - Ui::ItemTextBotNoMonoOptions()); - 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; + if (!_botAbout) { + return; + } else if (_botAbout->refresh() && recount && _contentWidth > 0) { + const auto view = _botAbout->view(); + const auto now = view ? view->resizeGetHeight(_contentWidth) : 0; + if (_botAbout->height != now) { + _botAbout->height = now; 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() { const auto visibleHeight = _scroll->height(); const auto itemsHeight = historyHeight() - _revealHeight; - int newHistoryPaddingTop = qMax(visibleHeight - itemsHeight - st::historyPaddingBottom, 0); - if (_botAbout && !_botAbout->info->text.isEmpty()) { - accumulate_max(newHistoryPaddingTop, st::msgMargin.top() + st::msgMargin.bottom() + st::msgPadding.top() + st::msgPadding.bottom() + st::msgNameFont->height + st::botDescSkip + _botAbout->height); + auto newHistoryPaddingTop = qMax(visibleHeight - itemsHeight - st::historyPaddingBottom, 0); + if (_botAbout) { + accumulate_max(newHistoryPaddingTop, _botAbout->height); } if (_botAbout && _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 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()); + _botAbout->top = qMin( + newHistoryPaddingTop - _botAbout->height, + qMax(0, (_scroll->height() - _botAbout->height) / 2)); } if (_historyPaddingTop != newHistoryPaddingTop) { @@ -3261,6 +3231,7 @@ void HistoryInner::leaveEventHook(QEvent *e) { } HistoryInner::~HistoryInner() { + _botAbout = nullptr; for (const auto &item : _animatedStickersPlayed) { if (const auto view = item->mainView()) { if (const auto media = view->media()) { @@ -3632,12 +3603,15 @@ void HistoryInner::mouseActionUpdate() { dragState = reactionState; lnkhost = reactionView; } else if (point.y() < _historyPaddingTop) { - if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) { - dragState = TextState(nullptr, _botAbout->info->text.getState( - point - _botAbout->rect.topLeft() - QPoint(st::msgPadding.left(), st::msgPadding.top() + st::botDescSkip + st::msgNameFont->height), - _botAbout->width)); + if (const auto view = _botAbout ? _botAbout->view() : nullptr) { + StateRequest request; + if (base::IsAltPressed()) { + 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); - lnkhost = _botAbout.get(); + lnkhost = view; } } else if (item) { if (item != _mouseActionItem || (m - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) { @@ -3947,16 +3921,22 @@ void HistoryInner::setCanHaveFromUserpicsSponsored(bool value) { int HistoryInner::itemTop(const HistoryItem *item) const { if (!item) { return -2; + } else if (_botAbout && item == _botAbout->item()) { + return _botAbout->top; } return itemTop(item->mainView()); } 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; } - auto top = (view->history() == _history) + const auto top = (view->history() == _history) ? historyTop() : (view->history() == _migrated ? migratedTop() @@ -3990,21 +3970,17 @@ auto HistoryInner::findViewForPinnedTracking(int top) const } void HistoryInner::notifyIsBotChanged() { - const auto newinfo = _peer->isUser() - ? _peer->asUser()->botInfo.get() - : nullptr; - if ((!newinfo && !_botAbout) - || (newinfo && _botAbout && _botAbout->info == newinfo)) { - return; - } - - if (newinfo) { - _botAbout = std::make_unique<BotAbout>(this, newinfo); - if (newinfo && !newinfo->inited) { - session().api().requestFullPeer(_peer); + if (const auto user = _peer->asUser()) { + if (const auto info = user->botInfo.get()) { + if (!_botAbout) { + _botAbout = std::make_unique<BotAbout>( + _history, + _history->delegateMixin()->delegate()); + } + if (!info->inited) { + session().api().requestFullPeer(_peer); + } } - } else { - _botAbout = nullptr; } } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 47ab8f01a..629cc7267 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -306,6 +306,9 @@ public: [[nodiscard]] bool isLocal() const { return _flags & MessageFlag::Local; } + [[nodiscard]] bool isFakeBotAbout() const { + return _flags & MessageFlag::FakeBotAbout; + } [[nodiscard]] bool isRegular() const; [[nodiscard]] bool isUploading() const; void sendFailed(); diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 0bab7aee2..8f13011cd 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -354,6 +354,20 @@ void DateBadge::paint( 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( not_null<ElementDelegate*> delegate, not_null<HistoryItem*> data, @@ -376,6 +390,9 @@ Element::Element( if (_context == Context::History) { history()->setHasPendingResizedItems(); } + if (data->isFakeBotAbout() && !data->history()->peer->isRepliesChat()) { + AddComponents(FakeBotAboutTop::Bit()); + } } not_null<ElementDelegate*> Element::delegate() const { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 7c2ab77e7..a2b9cb049 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -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 { std::unique_ptr<Ui::RippleAnimation> ripple; ClickHandlerPtr link; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index a3aa5e99c..e9c773ff9 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -530,6 +530,12 @@ QSize Message::performCountOptimalSize() { refreshInfoSkipBlock(); const auto media = this->media(); + const auto botTop = item->isFakeBotAbout() + ? Get<FakeBotAboutTop>() + : nullptr; + if (botTop) { + botTop->init(); + } auto maxWidth = 0; auto minHeight = 0; @@ -560,13 +566,14 @@ QSize Message::performCountOptimalSize() { } // Entry page is always a bubble bottom. + const auto withVisibleText = hasVisibleText(); auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); maxWidth = plainMaxWidth(); if (context() == Context::Replies && item->isDiscussionPost()) { maxWidth = std::max(maxWidth, st::msgMaxWidth); } - minHeight = hasVisibleText() ? text().minHeight() : 0; + minHeight = withVisibleText ? text().minHeight() : 0; if (reactionsInBubble) { const auto reactionsMaxWidth = st::msgPadding.left() + _reactions->maxWidth() @@ -604,9 +611,14 @@ QSize Message::performCountOptimalSize() { const auto innerWidth = maxWidth - st::msgPadding.left() - st::msgPadding.right(); - if (hasVisibleText() && maxWidth < plainMaxWidth()) { - minHeight -= text().minHeight(); - minHeight += text().countHeight(innerWidth); + if (withVisibleText) { + if (botTop) { + minHeight += botTop->height; + } + if (maxWidth < plainMaxWidth()) { + minHeight -= text().minHeight(); + minHeight += text().countHeight(innerWidth); + } } if (reactionsInBubble) { minHeight -= _reactions->minHeight(); @@ -678,6 +690,10 @@ QSize Message::performCountOptimalSize() { accumulate_max(maxWidth, entry->maxWidth()); minHeight += entry->minHeight(); } + if (withVisibleText && botTop) { + accumulate_max(maxWidth, botTop->maxWidth); + minHeight += botTop->height; + } } accumulate_max(maxWidth, minWidthForMedia()); } else if (media) { @@ -1467,6 +1483,15 @@ void Message::paintText( p.setPen(stm->historyTextFg); p.setFont(st::msgFont); 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, { .position = trect.topLeft(), .availableWidth = trect.width(), @@ -2281,6 +2306,8 @@ bool Message::getStateText( StateRequest request) const { if (!hasVisibleText()) { return false; + } else if (const auto botTop = Get<FakeBotAboutTop>()) { + trect.setY(trect.y() + botTop->height); } const auto item = data(); if (base::in_range(point.y(), trect.y(), trect.y() + trect.height())) { @@ -2886,7 +2913,7 @@ bool Message::drawBubble() const { const auto item = data(); if (isHidden()) { return false; - } else if (logEntryOriginal()) { + } else if (logEntryOriginal() || item->isFakeBotAbout()) { return true; } const auto media = this->media(); @@ -3376,8 +3403,9 @@ QRect Message::innerGeometry() const { } QRect Message::countGeometry() const { - const auto commentsRoot = (context() == Context::Replies) - && data()->isDiscussionPost(); + const auto item = data(); + const auto centeredView = item->isFakeBotAbout() + || (context() == Context::Replies && item->isDiscussionPost()); const auto media = this->media(); const auto mediaWidth = (media && media->isDisplayed()) ? media->width() @@ -3385,7 +3413,7 @@ QRect Message::countGeometry() const { const auto outbg = hasOutLayout(); const auto availableWidth = width() - st::msgMargin.left() - - (commentsRoot ? st::msgMargin.left() : st::msgMargin.right()); + - (centeredView ? st::msgMargin.left() : st::msgMargin.right()); auto contentLeft = (outbg && !delegate()->elementIsChatWide()) ? st::msgMargin.right() : st::msgMargin.left(); @@ -3412,10 +3440,10 @@ QRect Message::countGeometry() const { if (contentWidth < availableWidth && !delegate()->elementIsChatWide()) { if (outbg) { contentLeft += availableWidth - contentWidth; - } else if (commentsRoot) { + } else if (centeredView) { contentLeft += (availableWidth - contentWidth) / 2; } - } else if (contentWidth < availableWidth && commentsRoot) { + } else if (contentWidth < availableWidth && centeredView) { contentLeft += std::max( ((st::msgMaxWidth + 2 * st::msgPhotoSkip) - contentWidth) / 2, 0); @@ -3433,11 +3461,13 @@ Ui::BubbleRounding Message::countMessageRounding() const { const auto smallTop = isBubbleAttachedToPrevious(); const auto smallBottom = isBubbleAttachedToNext(); 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 || (media && media->skipBubbleTail()) || (keyboard != nullptr) - || (context() == Context::Replies && data()->isDiscussionPost()); + || item->isFakeBotAbout() + || (context() == Context::Replies && item->isDiscussionPost()); const auto right = !delegate()->elementIsChatWide() && hasOutLayout(); using Corner = Ui::BubbleCornerRounding; return Ui::BubbleRounding{ @@ -3480,16 +3510,19 @@ int Message::resizeContentGetHeight(int newWidth) { auto newHeight = minHeight(); const auto item = data(); + const auto botTop = item->isFakeBotAbout() + ? Get<FakeBotAboutTop>() + : nullptr; const auto media = this->media(); const auto mediaDisplayed = media ? media->isDisplayed() : false; const auto bubble = drawBubble(); // This code duplicates countGeometry() but also resizes media. - const auto commentsRoot = (context() == Context::Replies) - && data()->isDiscussionPost(); + const auto centeredView = item->isFakeBotAbout() + || (context() == Context::Replies && item->isDiscussionPost()); auto contentWidth = newWidth - st::msgMargin.left() - - (commentsRoot ? st::msgMargin.left() : st::msgMargin.right()); + - (centeredView ? st::msgMargin.left() : st::msgMargin.right()); if (hasFromPhoto()) { if (const auto size = rightActionSize()) { contentWidth -= size->width() + (st::msgPhotoSkip - st::historyFastShareSize); @@ -3540,7 +3573,14 @@ int Message::resizeContentGetHeight(int newWidth) { entry->resizeGetHeight(contentWidth); } } 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)) { newHeight += st::msgPadding.bottom(); if (mediaDisplayed) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index caf8c2e7a..a4e8a372d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -199,6 +199,10 @@ QSize Gif::countOptimalSize() { minHeight = adjustHeightForLessCrop( scaled, { maxWidth, minHeight }); + if (const auto botTop = _parent->Get<FakeBotAboutTop>()) { + accumulate_max(maxWidth, botTop->maxWidth); + minHeight += botTop->height; + } minHeight += st::mediaCaptionSkip + _caption.minHeight(); if (isBubbleBottom()) { minHeight += st::msgPadding.bottom(); @@ -236,11 +240,14 @@ QSize Gif::countCurrentSize(int newWidth) { if (_parent->hasBubble()) { accumulate_max(newWidth, _parent->minWidthForMedia()); if (!_caption.isEmpty()) { - const auto maxWithCaption = qMin( - st::msgMaxWidth, - (st::msgPadding.left() - + _caption.maxWidth() - + st::msgPadding.right())); + auto captionMaxWidth = st::msgPadding.left() + + _caption.maxWidth() + + st::msgPadding.right(); + const auto botTop = _parent->Get<FakeBotAboutTop>(); + if (botTop) { + accumulate_max(captionMaxWidth, botTop->maxWidth); + } + const auto maxWithCaption = qMin(st::msgMaxWidth, captionMaxWidth); newWidth = qMin(qMax(newWidth, maxWithCaption), thumbMaxWidth); newHeight = adjustHeightForLessCrop( scaled, @@ -248,6 +255,9 @@ QSize Gif::countCurrentSize(int newWidth) { const auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right(); + if (botTop) { + newHeight += botTop->height; + } newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); if (isBubbleBottom()) { newHeight += st::msgPadding.bottom(); @@ -349,12 +359,16 @@ void Gif::draw(Painter &p, const PaintContext &context) const { const auto outbg = context.outbg; const auto inWebPage = (_parent->media() != this); const auto isRound = _data->isVideoMessage(); + const auto botTop = _parent->Get<FakeBotAboutTop>(); const auto rounding = inWebPage ? std::optional<Ui::BubbleRounding>() : adjustedBubbleRoundingWithCaption(_caption); if (bubble) { if (!_caption.isEmpty()) { + if (botTop) { + painth -= botTop->height; + } painth -= st::mediaCaptionSkip + _caption.countHeight(captionw); if (isBubbleBottom()) { painth -= st::msgPadding.bottom(); @@ -674,10 +688,18 @@ void Gif::draw(Painter &p, const PaintContext &context) const { if (!unwrapped && !_caption.isEmpty()) { p.setPen(stm->historyTextFg); _parent->prepareCustomEmojiPaint(p, context, _caption); - _caption.draw(p, { - .position = QPoint( + auto top = painty + painth + st::mediaCaptionSkip; + if (botTop) { + botTop->text.drawLeftElided( + p, 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, .palette = &stm->textPalette, .spoiler = Ui::Text::DefaultSpoilerCache(), @@ -956,6 +978,9 @@ TextState Gif::textState(QPoint point, StateRequest request) const { request.forText())); return result; } + if (const auto botTop = _parent->Get<FakeBotAboutTop>()) { + painth -= botTop->height; + } painth -= st::mediaCaptionSkip; } const auto outbg = _parent->hasOutLayout(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index b65b946e9..78c4be6f3 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -187,6 +187,10 @@ QSize Photo::countOptimalSize() { minHeight = adjustHeightForLessCrop( dimensions, { maxWidth, minHeight }); + if (const auto botTop = _parent->Get<FakeBotAboutTop>()) { + accumulate_max(maxWidth, botTop->maxWidth); + minHeight += botTop->height; + } minHeight += st::mediaCaptionSkip + _caption.minHeight(); if (isBubbleBottom()) { minHeight += st::msgPadding.bottom(); @@ -214,11 +218,14 @@ QSize Photo::countCurrentSize(int newWidth) { newWidth = qMax(pix.width(), minWidth); auto newHeight = qMax(pix.height(), st::minPhotoSize); if (_parent->hasBubble() && !_caption.isEmpty()) { - const auto maxWithCaption = qMin( - st::msgMaxWidth, - (st::msgPadding.left() - + _caption.maxWidth() - + st::msgPadding.right())); + auto captionMaxWidth = st::msgPadding.left() + + _caption.maxWidth() + + st::msgPadding.right(); + const auto botTop = _parent->Get<FakeBotAboutTop>(); + if (botTop) { + accumulate_max(captionMaxWidth, botTop->maxWidth); + } + const auto maxWithCaption = qMin(st::msgMaxWidth, captionMaxWidth); newWidth = qMin(qMax(newWidth, maxWithCaption), thumbMaxWidth); newHeight = adjustHeightForLessCrop( dimensions, @@ -226,6 +233,9 @@ QSize Photo::countCurrentSize(int newWidth) { const auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right(); + if (botTop) { + newHeight += botTop->height; + } newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); if (isBubbleBottom()) { newHeight += st::msgPadding.bottom(); @@ -268,6 +278,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const { } } const auto radial = isRadialAnimation(); + const auto botTop = _parent->Get<FakeBotAboutTop>(); auto rthumb = style::rtlrect(paintx, painty, paintw, painth, width()); if (_serviceWidth > 0) { @@ -279,6 +290,9 @@ void Photo::draw(Painter &p, const PaintContext &context) const { if (bubble) { if (!_caption.isEmpty()) { painth -= st::mediaCaptionSkip + _caption.countHeight(captionw); + if (botTop) { + painth -= botTop->height; + } if (isBubbleBottom()) { painth -= st::msgPadding.bottom(); } @@ -348,10 +362,18 @@ void Photo::draw(Painter &p, const PaintContext &context) const { if (!_caption.isEmpty()) { p.setPen(stm->historyTextFg); _parent->prepareCustomEmojiPaint(p, context, _caption); - _caption.draw(p, { - .position = QPoint( + auto top = painty + painth + st::mediaCaptionSkip; + if (botTop) { + botTop->text.drawLeftElided( + p, 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, .palette = &stm->textPalette, .spoiler = Ui::Text::DefaultSpoilerCache(), @@ -592,6 +614,9 @@ TextState Photo::textState(QPoint point, StateRequest request) const { request.forText())); return result; } + if (const auto botTop = _parent->Get<FakeBotAboutTop>()) { + painth -= botTop->height; + } painth -= st::mediaCaptionSkip; } if (QRect(paintx, painty, paintw, painth).contains(point)) {