Support service messages reactions.

This commit is contained in:
John Preston 2024-12-04 13:48:25 +04:00
parent c1528f532e
commit 35e40be550
10 changed files with 319 additions and 222 deletions

View file

@ -462,16 +462,16 @@ HistoryItem::HistoryItem(
.from = data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0),
.date = data.vdate().v,
}) {
if (data.vaction().type() != mtpc_messageActionPhoneCall) {
createServiceFromMtp(data);
} else {
data.vaction().match([&](const MTPDmessageActionPhoneCall &data) {
createComponents(CreateConfig());
_media = std::make_unique<Data::MediaCall>(
this,
Data::ComputeCallData(
data.vaction().c_messageActionPhoneCall()));
Data::ComputeCallData(data));
setTextValue({});
}
}, [&](const auto &) {
createServiceFromMtp(data);
});
setReactions(data.vreactions());
applyTTL(data);
}

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item_components.h"
#include "history/history_item_helpers.h"
#include "base/unixtime.h"
#include "boxes/premium_preview_box.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "core/click_handler_types.h"
@ -30,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "chat_helpers/stickers_emoji_pack.h"
#include "payments/payments_reaction_process.h" // TryAddingPaidReaction.
#include "window/window_session_controller.h"
#include "ui/effects/path_shift_gradient.h"
#include "ui/effects/reaction_fly_animation.h"
@ -593,6 +595,10 @@ Element::Element(
}
}
bool Element::embedReactionsInBubble() const {
return false;
}
not_null<ElementDelegate*> Element::delegate() const {
return _delegate;
}
@ -1591,7 +1597,122 @@ bool Element::isSignedAuthorElided() const {
return false;
}
void Element::setupReactions(Element *replacing) {
refreshReactions();
auto animations = replacing
? replacing->takeReactionAnimations()
: base::flat_map<
Data::ReactionId,
std::unique_ptr<Ui::ReactionFlyAnimation>>();
if (!animations.empty()) {
const auto repainter = [=] { repaint(); };
for (const auto &[id, animation] : animations) {
animation->setRepaintCallback(repainter);
}
if (_reactions) {
_reactions->continueAnimations(std::move(animations));
}
}
}
void Element::refreshReactions() {
using namespace Reactions;
auto reactionsData = InlineListDataFromMessage(this);
if (reactionsData.reactions.empty()) {
setReactions(nullptr);
return;
}
if (!_reactions) {
const auto handlerFactory = [=](ReactionId id) {
const auto weak = base::make_weak(this);
return std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
const auto strong = weak.get();
if (!strong) {
return;
}
const auto item = strong->data();
const auto controller = ExtractController(context);
if (item->reactionsAreTags()) {
if (item->history()->session().premium()) {
const auto tag = Data::SearchTagToQuery(id);
HashtagClickHandler(tag).onClick(context);
} else if (controller) {
ShowPremiumPreviewBox(
controller,
PremiumFeature::TagsForMessages);
}
return;
}
if (id.paid()) {
Payments::TryAddingPaidReaction(
item,
weak.get(),
1,
std::nullopt,
controller->uiShow());
return;
} else {
const auto source = HistoryReactionSource::Existing;
item->toggleReaction(id, source);
}
if (const auto now = weak.get()) {
const auto chosen = now->data()->chosenReactions();
if (id.paid() || ranges::contains(chosen, id)) {
now->animateReaction({
.id = id,
});
}
}
});
};
setReactions(std::make_unique<InlineList>(
&history()->owner().reactions(),
handlerFactory,
[=] { customEmojiRepaint(); },
std::move(reactionsData)));
} else {
auto was = _reactions->computeTagsList();
_reactions->update(std::move(reactionsData), width());
auto now = _reactions->computeTagsList();
if (!was.empty() || !now.empty()) {
auto &owner = history()->owner();
owner.viewTagsChanged(this, std::move(was), std::move(now));
}
}
}
void Element::setReactions(std::unique_ptr<Reactions::InlineList> list) {
auto was = _reactions
? _reactions->computeTagsList()
: std::vector<Data::ReactionId>();
_reactions = std::move(list);
auto now = _reactions
? _reactions->computeTagsList()
: std::vector<Data::ReactionId>();
if (!was.empty() || !now.empty()) {
auto &owner = history()->owner();
owner.viewTagsChanged(this, std::move(was), std::move(now));
}
}
bool Element::updateReactions() {
const auto wasReactions = _reactions
? _reactions->currentSize()
: QSize();
refreshReactions();
const auto nowReactions = _reactions
? _reactions->currentSize()
: QSize();
return (wasReactions != nowReactions);
}
void Element::itemDataChanged() {
if (updateReactions()) {
history()->owner().requestViewResize(this);
} else {
repaint();
}
}
void Element::itemTextUpdated() {
@ -1615,6 +1736,9 @@ void Element::blockquoteExpandChanged() {
void Element::unloadHeavyPart() {
history()->owner().unregisterHeavyViewPart(this);
if (_reactions) {
_reactions->unloadCustomEmoji();
}
if (_media) {
_media->unloadHeavyPart();
}
@ -1915,9 +2039,6 @@ void Element::clickHandlerPressedChanged(
}
}
void Element::animateReaction(Ui::ReactionFlyAnimationArgs &&args) {
}
void Element::animateUnreadReactions() {
const auto &recent = data()->recentReactions();
for (const auto &[id, list] : recent) {
@ -1931,6 +2052,9 @@ auto Element::takeReactionAnimations()
-> base::flat_map<
Data::ReactionId,
std::unique_ptr<Ui::ReactionFlyAnimation>> {
if (_reactions) {
return _reactions->takeAnimations();
}
return {};
}
@ -1950,6 +2074,8 @@ QRect Element::effectIconGeometry() const {
}
Element::~Element() {
setReactions(nullptr);
// Delete media while owner still exists.
clearSpecialOnlyEmoji();
base::take(_media);
@ -2058,4 +2184,12 @@ int FindViewY(not_null<Element*> view, uint16 symbol, int yfrom) {
return origin.y() + (yfrom + ytill) / 2;
}
Window::SessionController *ExtractController(const ClickContext &context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
return controller;
}
return nullptr;
}
} // namespace HistoryView

View file

@ -338,6 +338,8 @@ public:
Element *replacing,
Flag serviceFlag);
[[nodiscard]] virtual bool embedReactionsInBubble() const;
[[nodiscard]] not_null<ElementDelegate*> delegate() const;
[[nodiscard]] not_null<HistoryItem*> data() const;
[[nodiscard]] not_null<History*> history() const;
@ -561,9 +563,9 @@ public:
[[nodiscard]] bool markSponsoredViewed(int shownFromTop) const;
virtual void animateReaction(Ui::ReactionFlyAnimationArgs &&args);
virtual void animateReaction(Ui::ReactionFlyAnimationArgs &&args) = 0;
void animateUnreadReactions();
[[nodiscard]] virtual auto takeReactionAnimations()
[[nodiscard]] auto takeReactionAnimations()
-> base::flat_map<
Data::ReactionId,
std::unique_ptr<Ui::ReactionFlyAnimation>>;
@ -617,6 +619,12 @@ protected:
void clearSpecialOnlyEmoji();
void checkSpecialOnlyEmoji();
void setupReactions(Element *replacing);
void refreshReactions();
bool updateReactions();
std::unique_ptr<Reactions::InlineList> _reactions;
private:
// This should be called only from previousInBlocksChanged()
// to add required bits to the Composer mask
@ -641,6 +649,7 @@ private:
void setTextWithLinks(
const TextWithEntities &text,
const std::vector<ClickHandlerPtr> &links = {});
void setReactions(std::unique_ptr<Reactions::InlineList> list);
struct TextWithLinks {
TextWithEntities text;
@ -673,4 +682,7 @@ private:
uint16 symbol,
int yfrom = 0);
[[nodiscard]] Window::SessionController *ExtractController(
const ClickContext &context);
} // namespace HistoryView

View file

@ -38,7 +38,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "main/main_session.h"
#include "payments/payments_reaction_process.h" // TryAddingPaidReaction.
#include "ui/text/text_options.h"
#include "ui/painter.h"
#include "window/themes/window_theme.h" // IsNightMode.
@ -54,15 +53,6 @@ namespace {
constexpr auto kPlayStatusLimit = 2;
const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_";
[[nodiscard]] Window::SessionController *ExtractController(
const ClickContext &context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
return controller;
}
return nullptr;
}
class KeyboardStyle : public ReplyKeyboard::Style {
public:
KeyboardStyle(const style::BotKeyboardButton &st);
@ -423,22 +413,8 @@ Message::Message(
}
initLogEntryOriginal();
initPsa();
refreshReactions();
auto animations = replacing
? replacing->takeReactionAnimations()
: base::flat_map<
Data::ReactionId,
std::unique_ptr<Ui::ReactionFlyAnimation>>();
setupReactions(replacing);
auto animation = replacing ? replacing->takeEffectAnimation() : nullptr;
if (!animations.empty()) {
const auto repainter = [=] { repaint(); };
for (const auto &[id, animation] : animations) {
animation->setRepaintCallback(repainter);
}
if (_reactions) {
_reactions->continueAnimations(std::move(animations));
}
}
if (animation) {
_bottomInfo.continueEffectAnimation(std::move(animation));
}
@ -461,21 +437,6 @@ Message::~Message() {
_fromNameStatus = nullptr;
checkHeavyPart();
}
setReactions(nullptr);
}
void Message::setReactions(std::unique_ptr<Reactions::InlineList> list) {
auto was = _reactions
? _reactions->computeTagsList()
: std::vector<Data::ReactionId>();
_reactions = std::move(list);
auto now = _reactions
? _reactions->computeTagsList()
: std::vector<Data::ReactionId>();
if (!was.empty() || !now.empty()) {
auto &owner = history()->owner();
owner.viewTagsChanged(this, std::move(was), std::move(now));
}
}
void Message::refreshRightBadge() {
@ -694,16 +655,6 @@ void Message::animateEffect(Ui::ReactionFlyAnimationArgs &&args) {
}
}
auto Message::takeReactionAnimations()
-> base::flat_map<
Data::ReactionId,
std::unique_ptr<Ui::ReactionFlyAnimation>> {
if (_reactions) {
return _reactions->takeAnimations();
}
return {};
}
auto Message::takeEffectAnimation()
-> std::unique_ptr<Ui::ReactionFlyAnimation> {
return _bottomInfo.takeEffectAnimation();
@ -2425,9 +2376,6 @@ bool Message::hasHeavyPart() const {
void Message::unloadHeavyPart() {
Element::unloadHeavyPart();
if (_reactions) {
_reactions->unloadCustomEmoji();
}
_comments = nullptr;
if (_fromNameStatus) {
_fromNameStatus->custom = nullptr;
@ -3446,73 +3394,6 @@ bool Message::embedReactionsInBubble() const {
return needInfoDisplay();
}
void Message::refreshReactions() {
using namespace Reactions;
auto reactionsData = InlineListDataFromMessage(this);
if (reactionsData.reactions.empty()) {
setReactions(nullptr);
return;
}
if (!_reactions) {
const auto handlerFactory = [=](ReactionId id) {
const auto weak = base::make_weak(this);
return std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
const auto strong = weak.get();
if (!strong) {
return;
}
const auto item = strong->data();
const auto controller = ExtractController(context);
if (item->reactionsAreTags()) {
if (item->history()->session().premium()) {
const auto tag = Data::SearchTagToQuery(id);
HashtagClickHandler(tag).onClick(context);
} else if (controller) {
ShowPremiumPreviewBox(
controller,
PremiumFeature::TagsForMessages);
}
return;
}
if (id.paid()) {
Payments::TryAddingPaidReaction(
item,
weak.get(),
1,
std::nullopt,
controller->uiShow());
return;
} else {
const auto source = HistoryReactionSource::Existing;
item->toggleReaction(id, source);
}
if (const auto now = weak.get()) {
const auto chosen = now->data()->chosenReactions();
if (id.paid() || ranges::contains(chosen, id)) {
now->animateReaction({
.id = id,
});
}
}
});
};
setReactions(std::make_unique<InlineList>(
&history()->owner().reactions(),
handlerFactory,
[=] { customEmojiRepaint(); },
std::move(reactionsData)));
} else {
auto was = _reactions->computeTagsList();
_reactions->update(std::move(reactionsData), width());
auto now = _reactions->computeTagsList();
if (!was.empty() || !now.empty()) {
auto &owner = history()->owner();
owner.viewTagsChanged(this, std::move(was), std::move(now));
}
}
}
void Message::validateInlineKeyboard(HistoryMessageReplyMarkup *markup) {
if (!markup
|| markup->inlineKeyboard
@ -3554,18 +3435,17 @@ void Message::validateFromNameText(PeerData *from) const {
}
}
void Message::itemDataChanged() {
bool Message::updateBottomInfo() {
const auto wasInfo = _bottomInfo.currentSize();
const auto wasReactions = _reactions
? _reactions->currentSize()
: QSize();
refreshReactions();
_bottomInfo.update(BottomInfoDataFromMessage(this), width());
const auto nowInfo = _bottomInfo.currentSize();
const auto nowReactions = _reactions
? _reactions->currentSize()
: QSize();
if (wasInfo != nowInfo || wasReactions != nowReactions) {
return (_bottomInfo.currentSize() != wasInfo);
}
void Message::itemDataChanged() {
const auto infoChanged = updateBottomInfo();
const auto reactionsChanged = updateReactions();
if (infoChanged || reactionsChanged) {
history()->owner().requestViewResize(this);
} else {
repaint();

View file

@ -78,7 +78,7 @@ public:
[[nodiscard]] const HistoryMessageEdited *displayedEditBadge() const;
[[nodiscard]] HistoryMessageEdited *displayedEditBadge();
[[nodiscard]] bool embedReactionsInBubble() const;
bool embedReactionsInBubble() const override;
int marginTop() const override;
int marginBottom() const override;
@ -159,10 +159,6 @@ public:
const base::flat_set<UserId> &changes) override;
void animateReaction(Ui::ReactionFlyAnimationArgs &&args) override;
auto takeReactionAnimations()
-> base::flat_map<
Data::ReactionId,
std::unique_ptr<Ui::ReactionFlyAnimation>> override;
void animateEffect(Ui::ReactionFlyAnimationArgs &&args) override;
auto takeEffectAnimation()
@ -180,6 +176,8 @@ private:
struct FromNameStatus;
struct RightAction;
bool updateBottomInfo();
void initLogEntryOriginal();
void initPsa();
void fromNameUpdated(int width) const;
@ -300,15 +298,12 @@ private:
[[nodiscard]] ClickHandlerPtr psaTooltipLink() const;
void psaTooltipToggled(bool shown) const;
void setReactions(std::unique_ptr<Reactions::InlineList> list);
void refreshRightBadge();
void refreshReactions();
void validateFromNameText(PeerData *from) const;
mutable std::unique_ptr<RightAction> _rightAction;
mutable ClickHandlerPtr _fastReplyLink;
mutable std::unique_ptr<ViewButton> _viewButton;
std::unique_ptr<Reactions::InlineList> _reactions;
std::unique_ptr<TopicButton> _topicButton;
mutable std::unique_ptr<CommentsButton> _comments;

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_service_message.h"
#include "history/view/media/history_view_media.h"
#include "history/view/reactions/history_view_reactions.h"
#include "history/view/history_view_cursor_state.h"
#include "history/history.h"
#include "history/history_item.h"
@ -18,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h"
#include "info/profile/info_profile_cover.h"
#include "ui/chat/chat_style.h"
#include "ui/effects/reaction_fly_animation.h"
#include "ui/text/text_options.h"
#include "ui/painter.h"
#include "ui/power_saving.h"
@ -405,6 +407,7 @@ Service::Service(
not_null<HistoryItem*> data,
Element *replacing)
: Element(delegate, data, replacing, Flag::ServiceMessage) {
setupReactions(replacing);
}
QRect Service::innerGeometry() const {
@ -423,7 +426,27 @@ QRect Service::countGeometry() const {
if (delegate()->elementIsChatWide()) {
result.setWidth(qMin(result.width(), st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
}
return result.marginsRemoved(st::msgServiceMargin);
auto margins = st::msgServiceMargin;
margins.setTop(marginTop());
return result.marginsRemoved(margins);
}
void Service::animateReaction(Ui::ReactionFlyAnimationArgs &&args) {
const auto item = data();
auto g = countGeometry();
if (g.width() < 1 || isHidden()) {
return;
}
const auto repainter = [=] { repaint(); };
if (_reactions) {
const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
const auto reactionsLeft = 0;
g.setHeight(g.height() - reactionsHeight);
const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip);
_reactions->animate(args.translated(-reactionsPosition), repainter);
}
}
QSize Service::performCountCurrentSize(int newWidth) {
@ -439,12 +462,12 @@ QSize Service::performCountCurrentSize(int newWidth) {
}
const auto media = this->media();
const auto mediaDisplayed = media && media->isDisplayed();
auto contentWidth = newWidth;
if (mediaDisplayed && media->hideServiceText()) {
newHeight += st::msgServiceMargin.top()
+ media->resizeGetHeight(newWidth)
+ st::msgServiceMargin.bottom();
} else if (!text().isEmpty()) {
auto contentWidth = newWidth;
if (delegate()->elementIsChatWide()) {
accumulate_min(contentWidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left());
}
@ -465,12 +488,24 @@ QSize Service::performCountCurrentSize(int newWidth) {
}
}
if (_reactions) {
newHeight += st::mediaInBubbleSkip
+ _reactions->resizeGetHeight(contentWidth);
if (hasRightLayout()) {
_reactions->flipToRight();
}
}
return { newWidth, newHeight };
}
QSize Service::performCountOptimalSize() {
validateText();
if (_reactions) {
_reactions->initDimensions();
}
if (const auto media = this->media()) {
media->initDimensions();
if (media->hideServiceText()) {
@ -487,7 +522,12 @@ bool Service::isHidden() const {
}
int Service::marginTop() const {
return st::msgServiceMargin.top();
auto result = st::msgServiceMargin.top();
result += displayedDateHeight();
if (const auto bar = Get<UnreadBar>()) {
result += bar->height();
}
return result;
}
int Service::marginBottom() const {
@ -499,42 +539,32 @@ void Service::draw(Painter &p, const PaintContext &context) const {
if (g.width() < 1) {
return;
}
const auto &margin = st::msgServiceMargin;
const auto st = context.st;
auto height = this->height() - margin.top() - margin.bottom();
auto dateh = 0;
auto unreadbarh = 0;
auto clip = context.clip;
if (auto date = Get<DateBadge>()) {
dateh = date->height();
p.translate(0, dateh);
clip.translate(0, -dateh);
height -= dateh;
}
if (const auto bar = Get<UnreadBar>()) {
unreadbarh = bar->height();
if (clip.intersects(QRect(0, 0, width(), unreadbarh))) {
auto unreadbarh = bar->height();
auto dateh = 0;
if (const auto date = Get<DateBadge>()) {
dateh = date->height();
}
if (context.clip.intersects(QRect(0, dateh, width(), unreadbarh))) {
p.translate(0, dateh);
bar->paint(
p,
context,
0,
width(),
delegate()->elementIsChatWide());
p.translate(0, -dateh);
}
p.translate(0, unreadbarh);
clip.translate(0, -unreadbarh);
height -= unreadbarh;
}
if (isHidden()) {
if (auto skiph = dateh + unreadbarh) {
p.translate(0, -skiph);
}
return;
}
paintHighlight(p, context, height);
paintHighlight(p, context, g.height());
p.setTextPalette(st->serviceTextPalette());
@ -542,13 +572,26 @@ void Service::draw(Painter &p, const PaintContext &context) const {
const auto mediaDisplayed = media && media->isDisplayed();
const auto onlyMedia = (mediaDisplayed && media->hideServiceText());
if (!onlyMedia) {
if (mediaDisplayed) {
height -= margin.top() + media->height();
if (_reactions) {
const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
const auto reactionsLeft = 0;
g.setHeight(g.height() - reactionsHeight);
const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip);
p.translate(reactionsPosition);
prepareCustomEmojiPaint(p, context, *_reactions);
_reactions->paint(p, context, g.width(), context.clip.translated(-reactionsPosition));
if (context.reactionInfo) {
context.reactionInfo->position = reactionsPosition;
}
const auto trect = QRect(g.left(), margin.top(), g.width(), height)
p.translate(-reactionsPosition);
}
if (!onlyMedia) {
const auto mediaSkip = mediaDisplayed ? (st::msgServiceMargin.top() + media->height()) : 0;
const auto trect = QRect(g.left(), g.top(), g.width(), g.height() - mediaSkip)
- st::msgServicePadding;
p.translate(0, g.top() - st::msgServiceMargin.top());
ServiceMessagePainter::PaintComplexBubble(
p,
context.st,
@ -556,6 +599,7 @@ void Service::draw(Painter &p, const PaintContext &context) const {
g.width(),
text(),
trect);
p.translate(0, -g.top() + st::msgServiceMargin.top());
p.setBrush(Qt::NoBrush);
p.setPen(st->msgServiceFg());
@ -575,15 +619,12 @@ void Service::draw(Painter &p, const PaintContext &context) const {
});
}
if (mediaDisplayed) {
const auto left = margin.left() + (g.width() - media->width()) / 2;
const auto top = margin.top() + (onlyMedia ? 0 : (height + margin.top()));
p.translate(left, top);
media->draw(p, context.translated(-left, -top).withSelection({}));
p.translate(-left, -top);
}
if (auto skiph = dateh + unreadbarh) {
p.translate(0, -skiph);
const auto left = g.left() + (g.width() - media->width()) / 2;
const auto top = g.top() + (onlyMedia ? 0 : (g.height() - media->height()));
const auto position = QPoint(left, top);
p.translate(position);
media->draw(p, context.translated(-position).withSelection({}));
p.translate(-position);
}
}
@ -596,12 +637,6 @@ PointState Service::pointState(QPoint point) const {
return PointState::Outside;
}
if (const auto dateh = displayedDateHeight()) {
g.setTop(g.top() + dateh);
}
if (const auto bar = Get<UnreadBar>()) {
g.setTop(g.top() + bar->height());
}
if (mediaDisplayed) {
const auto centerPadding = (g.width() - media->width()) / 2;
const auto r = g - QMargins(centerPadding, 0, centerPadding, 0);
@ -626,24 +661,26 @@ TextState Service::textState(QPoint point, StateRequest request) const {
return result;
}
if (const auto dateh = displayedDateHeight()) {
point.setY(point.y() - dateh);
g.setHeight(g.height() - dateh);
}
if (const auto bar = Get<UnreadBar>()) {
auto unreadbarh = bar->height();
point.setY(point.y() - unreadbarh);
g.setHeight(g.height() - unreadbarh);
if (_reactions) {
const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
const auto reactionsLeft = 0;
g.setHeight(g.height() - reactionsHeight);
const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip);
if (_reactions->getState(point - reactionsPosition, &result)) {
//result.symbol += visibleMediaTextLen + visibleTextLen;
return result;
}
}
if (onlyMedia) {
return media->textState(point - QPoint(st::msgServiceMargin.left() + (g.width() - media->width()) / 2, st::msgServiceMargin.top()), request);
return media->textState(point - QPoint(st::msgServiceMargin.left() + (g.width() - media->width()) / 2, g.top()), request);
} else if (mediaDisplayed) {
g.setHeight(g.height() - (st::msgServiceMargin.top() + media->height()));
}
const auto mediaLeft = st::msgServiceMargin.left()
+ (media ? ((g.width() - media->width()) / 2) : 0);
const auto mediaTop = st::msgServiceMargin.top()
const auto mediaTop = g.top()
+ g.height()
+ st::msgServiceMargin.top();
const auto mediaPoint = point - QPoint(mediaLeft, mediaTop);

View file

@ -54,6 +54,8 @@ public:
bool consumeHorizontalScroll(QPoint position, int delta) override;
void animateReaction(Ui::ReactionFlyAnimationArgs &&args) override;
private:
[[nodiscard]] QRect countGeometry() const;

View file

@ -225,11 +225,10 @@ void ServiceBox::draw(Painter &p, const PaintContext &context) const {
TextState ServiceBox::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent);
point.setY(point.y() - st::msgServiceGiftBoxTopSkip);
const auto content = contentRect();
const auto lookupSubtitleLink = [&] {
auto top = st::msgServiceGiftBoxTopSkip
+ content.top()
+ content.height();
auto top = content.top() + content.height();
const auto &padding = st::msgServiceGiftBoxTitlePadding;
top += padding.top();
if (!_title.isEmpty()) {

View file

@ -180,6 +180,21 @@ void InlineList::layoutButtons() {
_buttons = std::move(buttons);
}
InlineList::Dimension InlineList::countDimension(int width) const {
using Flag = InlineListData::Flag;
const auto inBubble = (_data.flags & Flag::InBubble);
const auto centered = (_data.flags & Flag::Centered);
const auto useWidth = centered
? std::min(width, st::chatGiveawayWidth)
: width;
const auto left = inBubble
? st::reactionInlineInBubbleLeft
: centered
? ((width - useWidth) / 2)
: 0;
return { .left = left, .width = useWidth };
}
InlineList::Button InlineList::prepareButtonWithId(const ReactionId &id) {
auto result = Button{ .id = id, .paid = id.paid()};
if (const auto customId = id.custom()) {
@ -258,9 +273,7 @@ QSize InlineList::countOptimalSize() {
if (_buttons.empty()) {
return _skipBlock;
}
const auto left = (_data.flags & InlineListData::Flag::InBubble)
? st::reactionInlineInBubbleLeft
: 0;
const auto left = countDimension(width()).left;
auto x = left;
const auto between = st::reactionInlineBetween;
const auto padding = st::reactionInlinePadding;
@ -308,23 +321,42 @@ QSize InlineList::countCurrentSize(int newWidth) {
}
using Flag = InlineListData::Flag;
const auto between = st::reactionInlineBetween;
const auto inBubble = (_data.flags & Flag::InBubble);
const auto left = inBubble ? st::reactionInlineInBubbleLeft : 0;
const auto dimension = countDimension(newWidth);
const auto left = dimension.left;
const auto width = dimension.width;
const auto centered = (_data.flags & Flag::Centered);
auto x = left;
auto y = 0;
for (auto &button : _buttons) {
const auto recenter = [&](int beforeIndex) {
const auto added = centered ? (left + width + between - x) : 0;
if (added <= 0) {
return;
}
const auto shift = added / 2;
for (auto j = beforeIndex; j != 0;) {
auto &button = _buttons[--j];
if (button.geometry.y() != y) {
break;
}
button.geometry.translate(shift, 0);
}
};
for (auto i = 0, count = int(_buttons.size()); i != count; ++i) {
auto &button = _buttons[i];
const auto size = button.geometry.size();
if (x > left && x + size.width() > newWidth) {
if (x > left && x + size.width() > left + width) {
recenter(i);
x = left;
y += size.height() + between;
}
button.geometry = QRect(QPoint(x, y), size);
x += size.width() + between;
}
recenter(_buttons.size());
const auto &last = _buttons.back().geometry;
const auto height = y + last.height();
const auto right = last.x() + last.width() + _skipBlock.width();
const auto add = (right > newWidth) ? _skipBlock.height() : 0;
const auto add = (right > width) ? _skipBlock.height() : 0;
return { newWidth, height + add };
}
@ -667,10 +699,9 @@ void InlineList::paintSingleBg(
bool InlineList::getState(
QPoint point,
not_null<TextState*> outResult) const {
const auto left = (_data.flags & InlineListData::Flag::InBubble)
? st::reactionInlineInBubbleLeft
: 0;
if (!QRect(left, 0, width() - left, height()).contains(point)) {
const auto dimension = countDimension(width());
const auto left = dimension.left;
if (!QRect(left, 0, dimension.width, height()).contains(point)) {
return false;
}
for (const auto &button : _buttons) {
@ -792,9 +823,9 @@ void InlineList::continueAnimations(base::flat_map<
}
}
InlineListData InlineListDataFromMessage(not_null<Message*> message) {
InlineListData InlineListDataFromMessage(not_null<Element*> view) {
using Flag = InlineListData::Flag;
const auto item = message->data();
const auto item = view->data();
auto result = InlineListData();
result.reactions = item->reactionsWithLocal();
if (const auto user = item->history()->peer->asUser()) {
@ -838,9 +869,10 @@ InlineListData InlineListDataFromMessage(not_null<Message*> message) {
}
}
}
result.flags = (message->hasOutLayout() ? Flag::OutLayout : Flag())
| (message->embedReactionsInBubble() ? Flag::InBubble : Flag())
| (item->reactionsAreTags() ? Flag::Tags : Flag());
result.flags = (view->hasOutLayout() ? Flag::OutLayout : Flag())
| (view->embedReactionsInBubble() ? Flag::InBubble : Flag())
| (item->reactionsAreTags() ? Flag::Tags : Flag())
| (item->isService() ? Flag::Centered : Flag());
return result;
}

View file

@ -26,7 +26,7 @@ class CustomEmoji;
namespace HistoryView {
using PaintContext = Ui::ChatPaintContext;
class Message;
class Element;
struct TextState;
struct UserpicInRow;
} // namespace HistoryView
@ -42,6 +42,7 @@ struct InlineListData {
OutLayout = 0x02,
Flipped = 0x04,
Tags = 0x08,
Centered = 0x10,
};
friend inline constexpr bool is_flag_type(Flag) { return true; };
using Flags = base::flags<Flag>;
@ -99,6 +100,10 @@ public:
[[nodiscard]] static QImage PrepareTagBg(QColor tagBg, QColor dotBg);
private:
struct Dimension {
int left = 0;
int width = 0;
};
struct Userpics {
QImage image;
std::vector<UserpicInRow> list;
@ -131,6 +136,7 @@ private:
void validateTagBg(const QColor &color) const;
QSize countOptimalSize() override;
[[nodiscard]] Dimension countDimension(int width) const;
const not_null<::Data::Reactions*> _owner;
const Fn<ClickHandlerPtr(ReactionId)> _handlerFactory;
@ -147,7 +153,7 @@ private:
};
[[nodiscard]] InlineListData InlineListDataFromMessage(
not_null<Message*> message);
not_null<Element*> view);
[[nodiscard]] ReactionId ReactionIdOfLink(const ClickHandlerPtr &link);