Track and render reactions as tags in Saved Messages.

This commit is contained in:
John Preston 2024-01-03 14:46:50 +04:00
parent 0faf801de7
commit 9b43d204e2
9 changed files with 216 additions and 39 deletions

View file

@ -20,6 +20,8 @@ namespace {
constexpr auto kPerPage = 50;
constexpr auto kFirstPerPage = 10;
constexpr auto kListPerPage = 100;
constexpr auto kListFirstPerPage = 20;
} // namespace
@ -82,7 +84,7 @@ void SavedMessages::sendLoadMore() {
MTP_int(_offsetDate),
MTP_int(_offsetId),
_offsetPeer ? _offsetPeer->input : MTP_inputPeerEmpty(),
MTP_int(kPerPage),
MTP_int(_offsetId ? kListPerPage : kListFirstPerPage),
MTP_long(0)) // hash
).done([=](const MTPmessages_SavedDialogs &result) {
apply(result, false);

View file

@ -313,6 +313,8 @@ enum class MessageFlag : uint64 {
ShowSimilarChannels = (1ULL << 41),
Sponsored = (1ULL << 42),
ReactionsAreTags = (1ULL << 43),
};
inline constexpr bool is_flag_type(MessageFlag) { return true; }
using MessageFlags = base::flags<MessageFlag>;

View file

@ -2429,6 +2429,10 @@ const std::vector<Data::MessageReaction> &HistoryItem::reactions() const {
return _reactions ? _reactions->list() : kEmpty;
}
bool HistoryItem::reactionsAreTags() const {
return _flags & MessageFlag::ReactionsAreTags;
}
auto HistoryItem::recentReactions() const
-> const base::flat_map<
Data::ReactionId,
@ -3556,31 +3560,40 @@ bool HistoryItem::changeReactions(const MTPMessageReactions *reactions) {
}
if (!reactions) {
_flags &= ~MessageFlag::CanViewReactions;
if (_history->peer->isSelf()) {
_flags |= MessageFlag::ReactionsAreTags;
}
return (base::take(_reactions) != nullptr);
}
return reactions->match([&](const MTPDmessageReactions &data) {
if (data.is_can_see_list()) {
_flags |= MessageFlag::CanViewReactions;
} else {
_flags &= ~MessageFlag::CanViewReactions;
const auto &data = reactions->data();
const auto empty = data.vresults().v.isEmpty();
if (data.is_reactions_as_tags()
|| (empty && _history->peer->isSelf())) {
_flags |= MessageFlag::ReactionsAreTags;
} else {
_flags &= ~MessageFlag::ReactionsAreTags;
}
if (data.is_can_see_list()) {
_flags |= MessageFlag::CanViewReactions;
} else {
_flags &= ~MessageFlag::CanViewReactions;
}
if (empty) {
return (base::take(_reactions) != nullptr);
} else if (!_reactions) {
_reactions = std::make_unique<Data::MessageReactions>(this);
}
const auto min = data.is_min();
const auto &list = data.vresults().v;
const auto &recent = data.vrecent_reactions().value_or_empty();
if (min && hasUnreadReaction()) {
// We can't update reactions from min if we have unread.
if (_reactions->checkIfChanged(list, recent, min)) {
updateReactionsUnknown();
}
if (data.vresults().v.isEmpty()) {
return (base::take(_reactions) != nullptr);
} else if (!_reactions) {
_reactions = std::make_unique<Data::MessageReactions>(this);
}
const auto min = data.is_min();
const auto &list = data.vresults().v;
const auto &recent = data.vrecent_reactions().value_or_empty();
if (min && hasUnreadReaction()) {
// We can't update reactions from min if we have unread.
if (_reactions->checkIfChanged(list, recent, min)) {
updateReactionsUnknown();
}
return false;
}
return _reactions->change(list, recent, min);
});
return false;
}
return _reactions->change(list, recent, min);
}
void HistoryItem::applyTTL(const MTPDmessage &data) {

View file

@ -454,6 +454,7 @@ public:
not_null<UserData*> from) const;
[[nodiscard]] crl::time lastReactionsRefreshTime() const;
[[nodiscard]] bool reactionsAreTags() const;
[[nodiscard]] bool hasDirectLink() const;
[[nodiscard]] bool changesWallPaper() const;

View file

@ -2917,8 +2917,12 @@ bool Message::isSignedAuthorElided() const {
bool Message::embedReactionsInBottomInfo() const {
const auto item = data();
const auto user = item->history()->peer->asUser();
if (!user || user->isPremium() || user->session().premium()) {
if (!user
|| user->isPremium()
|| user->isSelf()
|| user->session().premium()) {
// Only in messages of a non premium user with a non premium user.
// In saved messages we use reactions for tags, we don't embed them.
return false;
}
auto seenMy = false;

View file

@ -55,6 +55,7 @@ struct InlineList::Button {
int count = 0;
int countTextWidth = 0;
bool chosen = false;
bool tag = false;
};
InlineList::InlineList(
@ -118,6 +119,7 @@ void InlineList::layoutButtons() {
) | ranges::views::transform([](const MessageReaction &reaction) {
return not_null{ &reaction };
}) | ranges::to_vector;
const auto tags = _data.flags & Data::Flag::Tags;
const auto &list = _owner->list(::Data::Reactions::Type::All);
ranges::sort(sorted, [&](
not_null<const MessageReaction*> a,
@ -142,8 +144,10 @@ void InlineList::layoutButtons() {
buttons.push_back((i != end(_buttons))
? std::move(*i)
: prepareButtonWithId(id));
const auto j = _data.recent.find(id);
if (j != end(_data.recent) && !j->second.empty()) {
if (tags) {
setButtonTag(buttons.back());
} else if (const auto j = _data.recent.find(id)
; j != end(_data.recent) && !j->second.empty()) {
setButtonUserpics(buttons.back(), j->second);
} else {
setButtonCount(buttons.back(), reaction->count);
@ -168,12 +172,22 @@ InlineList::Button InlineList::prepareButtonWithId(const ReactionId &id) {
return result;
}
void InlineList::setButtonTag(Button &button) {
if (button.tag) {
return;
}
button.userpics = nullptr;
button.count = 0;
button.tag = true;
}
void InlineList::setButtonCount(Button &button, int count) {
if (button.count == count && !button.userpics) {
if (!button.tag && button.count == count && !button.userpics) {
return;
}
button.userpics = nullptr;
button.count = count;
button.tag = false;
button.countText = Lang::FormatCountToShort(count).string;
button.countTextWidth = st::semiboldFont->width(button.countText);
}
@ -181,6 +195,7 @@ void InlineList::setButtonCount(Button &button, int count) {
void InlineList::setButtonUserpics(
Button &button,
const std::vector<not_null<PeerData*>> &peers) {
button.tag = false;
if (!button.userpics) {
button.userpics = std::make_unique<Userpics>();
}
@ -228,6 +243,10 @@ QSize InlineList::countOptimalSize() {
const auto between = st::reactionInlineBetween;
const auto padding = st::reactionInlinePadding;
const auto size = st::reactionInlineSize;
const auto widthBaseTag = padding.left()
+ size
+ st::reactionInlineTagSkip
+ padding.right();
const auto widthBaseCount = padding.left()
+ size
+ st::reactionInlineSkip
@ -245,7 +264,9 @@ QSize InlineList::countOptimalSize() {
};
const auto height = padding.top() + size + padding.bottom();
for (auto &button : _buttons) {
const auto width = button.userpics
const auto width = button.tag
? widthBaseTag
: button.userpics
? (widthBaseUserpics + userpicsWidth(button))
: (widthBaseCount + button.countTextWidth);
button.geometry.setSize({ width, height });
@ -336,7 +357,8 @@ void InlineList::paint(
const auto padding = st::reactionInlinePadding;
const auto size = st::reactionInlineSize;
const auto skip = (size - st::reactionInlineImage) / 2;
const auto inbubble = (_data.flags & InlineListData::Flag::InBubble);
const auto tags = (_data.flags & Data::Flag::Tags);
const auto inbubble = (_data.flags & Data::Flag::InBubble);
const auto flipped = (_data.flags & Data::Flag::Flipped);
p.setFont(st::semiboldFont);
for (const auto &button : _buttons) {
@ -366,21 +388,26 @@ void InlineList::paint(
if (bubbleProgress > 0.) {
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
auto opacity = 1.;
auto color = QColor();
if (inbubble) {
if (!chosen) {
p.setOpacity(bubbleProgress * (context.outbg
opacity = bubbleProgress * (context.outbg
? kOutNonChosenOpacity
: kInNonChosenOpacity));
: kInNonChosenOpacity);
} else if (!bubbleReady) {
p.setOpacity(bubbleProgress);
opacity = bubbleProgress;
}
p.setBrush(stm->msgFileBg);
color = stm->msgFileBg->c;
} else {
if (!bubbleReady) {
p.setOpacity(bubbleProgress);
opacity = bubbleProgress;
}
p.setBrush(chosen ? st->msgServiceFg() : st->msgServiceBg());
color = (chosen
? st->msgServiceFg()
: st->msgServiceBg())->c;
}
const auto radius = geometry.height() / 2.;
const auto fill = geometry.marginsAdded({
flipped ? bubbleSkip : 0,
@ -388,7 +415,7 @@ void InlineList::paint(
flipped ? 0 : bubbleSkip,
0,
});
p.drawRoundedRect(fill, radius, radius);
paintSingleBg(p, fill, color, opacity);
if (inbubble && !chosen) {
p.setOpacity(bubbleProgress);
}
@ -434,7 +461,8 @@ void InlineList::paint(
.target = image,
});
}
if (bubbleProgress == 0.) {
if (tags || bubbleProgress == 0.) {
p.setOpacity(1.);
continue;
}
resolveUserpicsImage(button);
@ -479,6 +507,115 @@ void InlineList::paint(
}
}
void InlineList::validateTagBg(const QColor &color) const {
if (!_tagBg.isNull() && _tagBgColor == color) {
return;
}
_tagBgColor = color;
const auto padding = st::reactionInlinePadding;
const auto size = st::reactionInlineSize;
const auto width = padding.left()
+ size
+ st::reactionInlineTagSkip
+ padding.right();
const auto height = padding.top() + size + padding.bottom();
const auto ratio = style::DevicePixelRatio();
auto mask = QImage(
QSize(width, height) * ratio,
QImage::Format_ARGB32_Premultiplied);
mask.setDevicePixelRatio(ratio);
mask.fill(Qt::transparent);
auto p = QPainter(&mask);
auto path = QPainterPath();
const auto arrow = st::reactionInlineTagArrow;
const auto rradius = st::reactionInlineTagRightRadius * 1.;
const auto radius = st::reactionInlineTagLeftRadius - rradius;
const auto fg = QColor(255, 255, 255);
auto pen = QPen(fg);
pen.setWidthF(rradius * 2.);
pen.setJoinStyle(Qt::RoundJoin);
const auto rect = QRectF(0, 0, width, height).marginsRemoved(
{ rradius, rradius, rradius, rradius });
const auto right = rect.x() + rect.width();
const auto bottom = rect.y() + rect.height();
path.moveTo(rect.x() + radius, rect.y());
path.lineTo(right - arrow, rect.y());
path.lineTo(right, rect.y() + rect.height() / 2);
path.lineTo(right - arrow, bottom);
path.lineTo(rect.x() + radius, bottom);
path.arcTo(QRectF(rect.x(), bottom - radius * 2, radius * 2, radius * 2), 270, -90);
path.lineTo(rect.x(), rect.y() + radius);
path.arcTo(QRectF(rect.x(), rect.y(), radius * 2, radius * 2), 180, -90);
path.closeSubpath();
const auto dsize = st::reactionInlineTagDot;
const auto dot = QRectF(
right - st::reactionInlineTagDotSkip - dsize,
rect.y() + (rect.height() - dsize) / 2.,
dsize,
dsize);
auto hq = PainterHighQualityEnabler(p);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.setPen(pen);
p.setBrush(fg);
p.drawPath(path);
p.setPen(Qt::NoPen);
p.setBrush(QColor(255, 255, 255, 255 * 0.6));
p.drawEllipse(dot);
p.end();
_tagBg = style::colorizeImage(mask, color);
}
void InlineList::paintSingleBg(
Painter &p,
const QRect &fill,
const QColor &color,
float64 opacity) const {
p.setOpacity(opacity);
if (!(_data.flags & Data::Flag::Tags)) {
const auto radius = fill.height() / 2.;
p.setBrush(color);
p.drawRoundedRect(fill, radius, radius);
return;
}
validateTagBg(color);
const auto ratio = style::DevicePixelRatio();
const auto left = st::reactionInlineTagLeftRadius;
const auto right = (_tagBg.width() / ratio) - left;
Assert(right > 0);
const auto useLeft = std::min(fill.width(), left);
p.drawImage(
QRect(fill.x(), fill.y(), useLeft, fill.height()),
_tagBg,
QRect(0, 0, useLeft * ratio, _tagBg.height()));
const auto middle = fill.width() - left - right;
if (middle > 0) {
p.fillRect(fill.x() + left, fill.y(), middle, fill.height(), color);
}
if (const auto useRight = fill.width() - left; useRight > 0) {
p.drawImage(
QRect(
fill.x() + fill.width() - useRight,
fill.y(),
useRight,
fill.height()),
_tagBg,
QRect(_tagBg.width() - useRight * ratio,
0,
useRight * ratio,
_tagBg.height()));
}
}
bool InlineList::getState(
QPoint point,
not_null<TextState*> outResult) const {
@ -654,7 +791,8 @@ InlineListData InlineListDataFromMessage(not_null<Message*> message) {
}
}
result.flags = (message->hasOutLayout() ? Flag::OutLayout : Flag())
| (message->embedReactionsInBubble() ? Flag::InBubble : Flag());
| (message->embedReactionsInBubble() ? Flag::InBubble : Flag())
| (item->reactionsAreTags() ? Flag::Tags : Flag());
return result;
}

View file

@ -41,6 +41,7 @@ struct InlineListData {
InBubble = 0x01,
OutLayout = 0x02,
Flipped = 0x04,
Tags = 0x08,
};
friend inline constexpr bool is_flag_type(Flag) { return true; };
using Flags = base::flags<Flag>;
@ -103,6 +104,7 @@ private:
void layout();
void layoutButtons();
void setButtonTag(Button &button);
void setButtonCount(Button &button, int count);
void setButtonUserpics(
Button &button,
@ -115,6 +117,13 @@ private:
QPoint innerTopLeft,
const PaintContext &context,
const QColor &textColor) const;
void paintSingleBg(
Painter &p,
const QRect &fill,
const QColor &color,
float64 opacity) const;
void validateTagBg(const QColor &color) const;
QSize countOptimalSize() override;
@ -124,6 +133,8 @@ private:
Data _data;
std::vector<Button> _buttons;
QSize _skipBlock;
mutable QImage _tagBg;
mutable QColor _tagBgColor;
mutable QImage _customCache;
mutable int _customSkip = 0;
bool _hasCustomEmoji = false;

View file

@ -851,6 +851,12 @@ reactionInlinePadding: margins(5px, 2px, 7px, 2px);
reactionInlineSize: 18px;
reactionInlineImage: 32px;
reactionInlineSkip: 3px;
reactionInlineTagSkip: 6px;
reactionInlineTagLeftRadius: 6px;
reactionInlineTagRightRadius: 3px;
reactionInlineTagArrow: 5px;
reactionInlineTagDot: 5px;
reactionInlineTagDotSkip: 2px;
reactionInlineBetween: 4px;
reactionInlineInBubbleLeft: -3px;
reactionInlineUserpicsPadding: margins(1px, 1px, 1px, 1px);

View file

@ -440,7 +440,7 @@ void EmptyUserpic::PaintHiddenAuthor(
{ 0., st::premiumButtonBg2->c },
{ 1., st::premiumButtonBg3->c },
});
const auto &fg = st::historyPeerUserpicFg;
const auto &fg = st::premiumButtonFg;
PaintHiddenAuthor(p, x, y, outerWidth, size, QBrush(bg), fg);
}