Show tag names in Saved Messages.

This commit is contained in:
John Preston 2024-01-26 11:12:11 +04:00
parent 55a174190e
commit 32462fca9b
7 changed files with 193 additions and 69 deletions

View file

@ -422,10 +422,7 @@ void Reactions::scheduleMyTagsUpdate() {
return;
}
_myTagsUpdateScheduled = false;
_myTagsIds = _myTagsInfo | ranges::views::transform(
&MyTagInfo::id
) | ranges::to_vector;
_myTags = resolveByIds(_myTagsIds, _unresolvedMyTags);
_myTags = resolveByInfos(_myTagsInfo, _unresolvedMyTags);
_myTagsUpdated.fire({});
});
}
@ -854,10 +851,7 @@ void Reactions::updateGeneric(const MTPDmessages_stickerSet &data) {
void Reactions::updateMyTags(const MTPDmessages_savedReactionTags &data) {
_myTagsHash = data.vhash().v;
_myTagsInfo = ListFromMTP(data);
_myTagsIds = _myTagsInfo | ranges::views::transform(
&MyTagInfo::id
) | ranges::to_vector;
_myTags = resolveByIds(_myTagsIds, _unresolvedMyTags);
_myTags = resolveByInfos(_myTagsInfo, _unresolvedMyTags);
_myTagsUpdated.fire({});
}
@ -927,7 +921,7 @@ void Reactions::customEmojiResolveDone(not_null<DocumentData*> document) {
}
if (myTag) {
_unresolvedMyTags.erase(k);
_myTags = resolveByIds(_myTagsIds, _unresolvedMyTags);
_myTags = resolveByInfos(_myTagsInfo, _unresolvedMyTags);
}
if (tag) {
_unresolvedTags.erase(l);
@ -980,6 +974,41 @@ std::vector<Reaction> Reactions::resolveByIds(
return result;
}
std::optional<Reaction> Reactions::resolveByInfo(const MyTagInfo &info) {
const auto withInfo = [&](Reaction reaction) {
reaction.title = info.title;
reaction.count = info.count;
return reaction;
};
if (const auto emoji = info.id.emoji(); !emoji.isEmpty()) {
const auto i = ranges::find(_available, info.id, &Reaction::id);
if (i != end(_available)) {
return withInfo(*i);
}
} else if (const auto customId = info.id.custom()) {
const auto document = _owner->document(customId);
if (document->sticker()) {
return withInfo(CustomReaction(document));
}
}
return {};
}
std::vector<Reaction> Reactions::resolveByInfos(
const std::vector<MyTagInfo> &infos,
base::flat_set<ReactionId> &unresolved) {
auto result = std::vector<Reaction>();
result.reserve(infos.size());
for (const auto &tag : infos) {
if (const auto resolved = resolveByInfo(tag)) {
result.push_back(*resolved);
} else if (unresolved.emplace(tag.id).second) {
resolve(tag.id);
}
}
return result;
}
void Reactions::resolve(const ReactionId &id) {
if (const auto emoji = id.emoji(); !emoji.isEmpty()) {
refreshDefault();

View file

@ -34,6 +34,7 @@ struct Reaction {
//not_null<DocumentData*> activateEffects;
DocumentData *centerIcon = nullptr;
DocumentData *aroundAnimation = nullptr;
int count = 0;
bool active = false;
bool premium = false;
};
@ -167,6 +168,11 @@ private:
[[nodiscard]] std::vector<Reaction> resolveByIds(
const std::vector<ReactionId> &ids,
base::flat_set<ReactionId> &unresolved);
[[nodiscard]] std::optional<Reaction> resolveByInfo(
const MyTagInfo &info);
[[nodiscard]] std::vector<Reaction> resolveByInfos(
const std::vector<MyTagInfo> &infos,
base::flat_set<ReactionId> &unresolved);
void resolve(const ReactionId &id);
void applyFavorite(const ReactionId &id);
void scheduleMyTagsUpdate();
@ -193,7 +199,6 @@ private:
std::vector<ReactionId> _recentIds;
base::flat_set<ReactionId> _unresolvedRecent;
std::vector<Reaction> _myTags;
std::vector<ReactionId> _myTagsIds;
std::vector<MyTagInfo> _myTagsInfo;
base::flat_set<ReactionId> _unresolvedMyTags;
std::vector<Reaction> _tags;

View file

@ -19,10 +19,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_dialogs.h"
namespace Dialogs {
namespace {
[[nodiscard]] QString ComposeText(const Data::Reaction &tag) {
auto result = tag.title;
if (!result.isEmpty() && tag.count > 0) {
result.append(' ');
}
if (tag.count > 0) {
result.append(QString::number(tag.count));
}
return TextUtilities::SingleLine(result);
}
} // namespace
struct SearchTags::Tag {
Data::ReactionId id;
std::unique_ptr<Ui::Text::CustomEmoji> custom;
QString text;
int textWidth = 0;
mutable QImage image;
QRect geometry;
ClickHandlerPtr link;
@ -75,7 +91,7 @@ void SearchTags::fill(const std::vector<Data::Reaction> &list) {
}
}));
};
const auto push = [&](Data::ReactionId id) {
const auto push = [&](Data::ReactionId id, const QString &text) {
const auto customId = id.custom();
_tags.push_back({
.id = id,
@ -84,6 +100,8 @@ void SearchTags::fill(const std::vector<Data::Reaction> &list) {
customId,
[=] { _repaintRequests.fire({}); })
: nullptr),
.text = text,
.textWidth = st::reactionInlineTagFont->width(text),
.link = link(id),
.selected = ranges::contains(selected, id),
});
@ -92,11 +110,11 @@ void SearchTags::fill(const std::vector<Data::Reaction> &list) {
}
};
for (const auto &reaction : list) {
push(reaction.id);
push(reaction.id, ComposeText(reaction));
}
for (const auto &reaction : _added) {
if (!ranges::contains(_tags, reaction, &Tag::id)) {
push(reaction);
push(reaction, QString());
}
}
if (_width > 0) {
@ -107,26 +125,27 @@ void SearchTags::fill(const std::vector<Data::Reaction> &list) {
void SearchTags::layout() {
Expects(_width > 0);
if (_tags.empty()) {
_height = 0;
return;
}
const auto &bg = validateBg(false);
const auto skip = st::dialogsSearchTagSkip;
const auto size = bg.size() / bg.devicePixelRatio();
const auto xsingle = size.width() + skip.x();
const auto ysingle = size.height() + skip.y();
const auto columns = std::max((_width + skip.x()) / xsingle, 1);
const auto rows = (_tags.size() + columns - 1) / columns;
for (auto row = 0; row != rows; ++row) {
for (auto column = 0; column != columns; ++column) {
const auto index = row * columns + column;
if (index >= _tags.size()) {
break;
}
const auto x = column * xsingle;
const auto y = row * ysingle;
_tags[index].geometry = QRect(QPoint(x, y), size);
const auto xbase = size.width();
const auto ybase = size.height();
auto x = 0;
auto y = 0;
for (auto &tag : _tags) {
const auto width = xbase + tag.textWidth;
if (x > 0 && x + width > _width) {
x = 0;
y += ybase + skip.y();
}
tag.geometry = QRect(x, y, width, xbase);
x += width + skip.x();
}
const auto bottom = st::dialogsSearchTagBottom;
_height = rows ? (rows * ysingle - skip.y() + bottom) : 0;
_height = y + ybase + st::dialogsSearchTagBottom;
}
void SearchTags::resizeToWidth(int width) {
@ -212,7 +231,8 @@ void SearchTags::paint(
const auto padding = st::reactionInlinePadding;
for (const auto &tag : _tags) {
const auto geometry = tag.geometry.translated(position);
p.drawImage(geometry.topLeft(), validateBg(tag.selected));
paintBackground(p, geometry, tag.selected);
paintText(p, geometry, tag);
if (!tag.custom && tag.image.isNull()) {
tag.image = _owner->reactions().resolveImageFor(
tag.id,
@ -239,16 +259,59 @@ void SearchTags::paint(
}
}
void SearchTags::paintBackground(
QPainter &p,
QRect geometry,
bool selected) const {
const auto &image = validateBg(selected);
const auto ratio = int(image.devicePixelRatio());
const auto size = image.size() / ratio;
if (const auto fill = geometry.width() - size.width(); fill > 0) {
const auto left = size.width() / 2;
const auto right = size.width() - left;
const auto x = geometry.x();
const auto y = geometry.y();
p.drawImage(
QRect(x, y, left, size.height()),
image,
QRect(QPoint(), QSize(left, size.height()) * ratio));
p.fillRect(
QRect(x + left, y, fill, size.height()),
bgColor(selected));
p.drawImage(
QRect(x + left + fill, y, right, size.height()),
image,
QRect(left * ratio, 0, right * ratio, size.height() * ratio));
} else {
p.drawImage(geometry.topLeft(), image);
}
}
void SearchTags::paintText(QPainter &p, QRect geometry, const Tag &tag) const {
using namespace HistoryView::Reactions;
if (tag.text.isEmpty()) {
return;
}
p.setPen(tag.selected ? st::dialogsTextFgActive : st::windowSubTextFg);
p.setFont(st::reactionInlineTagFont);
const auto x = geometry.x() + st::reactionInlineTagNamePosition.x();
const auto y = geometry.y() + st::reactionInlineTagNamePosition.y();
p.drawText(x, y + st::reactionInlineTagFont->ascent, tag.text);
}
QColor SearchTags::bgColor(bool selected) const {
return selected
? st::dialogsBgActive->c
: st::dialogsBgOver->c;
}
const QImage &SearchTags::validateBg(bool selected) const {
using namespace HistoryView::Reactions;
auto &image = selected ? _selectedBg : _normalBg;
if (image.isNull()) {
const auto tagBg = selected
? st::dialogsBgActive->c
: st::dialogsBgOver->c;
const auto dotBg = selected
? anim::with_alpha(tagBg, InlineList::TagDotAlpha())
: st::windowSubTextFg->c;
const auto tagBg = bgColor(selected);
const auto dotBg = st::transparent->c;
image = InlineList::PrepareTagBg(tagBg, dotBg);
}
return image;

View file

@ -59,7 +59,13 @@ private:
const QColor &textColor) const;
void layout();
[[nodiscard]] std::vector<Data::ReactionId> collectSelected() const;
[[nodiscard]] QColor bgColor(bool selected) const;
[[nodiscard]] const QImage &validateBg(bool selected) const;
void paintBackground(
QPainter &p,
QRect geometry,
bool selected) const;
void paintText(QPainter &p, QRect geometry, const Tag &tag) const;
const not_null<Data::Session*> _owner;
std::vector<Data::ReactionId> _added;

View file

@ -51,9 +51,9 @@ struct InlineList::Button {
mutable std::unique_ptr<Ui::Text::CustomEmoji> custom;
std::unique_ptr<Userpics> userpics;
ReactionId id;
QString countText;
QString text;
int textWidth = 0;
int count = 0;
int countTextWidth = 0;
bool chosen = false;
bool tag = false;
};
@ -120,20 +120,23 @@ void InlineList::layoutButtons() {
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,
not_null<const MessageReaction*> b) {
const auto acount = a->count - (a->my ? 1 : 0);
const auto bcount = b->count - (b->my ? 1 : 0);
if (acount > bcount) {
return true;
} else if (acount < bcount) {
return false;
}
return ranges::find(list, a->id, &::Data::Reaction::id)
< ranges::find(list, b->id, &::Data::Reaction::id);
});
const auto &infos = _owner->myTagsInfo();
if (!tags) {
const auto &list = _owner->list(::Data::Reactions::Type::All);
ranges::sort(sorted, [&](
not_null<const MessageReaction*> a,
not_null<const MessageReaction*> b) {
const auto acount = a->count - (a->my ? 1 : 0);
const auto bcount = b->count - (b->my ? 1 : 0);
if (acount > bcount) {
return true;
} else if (acount < bcount) {
return false;
}
return ranges::find(list, a->id, &::Data::Reaction::id)
< ranges::find(list, b->id, &::Data::Reaction::id);
});
}
_hasCustomEmoji = false;
auto buttons = std::vector<Button>();
@ -145,7 +148,10 @@ void InlineList::layoutButtons() {
? std::move(*i)
: prepareButtonWithId(id));
if (tags) {
setButtonTag(buttons.back());
const auto i = ranges::find(infos, id, &::Data::MyTagInfo::id);
setButtonTag(
buttons.back(),
(i == end(infos)) ? QString() : i->title);
} else if (const auto j = _data.recent.find(id)
; j != end(_data.recent) && !j->second.empty()) {
setButtonUserpics(buttons.back(), j->second);
@ -172,13 +178,15 @@ InlineList::Button InlineList::prepareButtonWithId(const ReactionId &id) {
return result;
}
void InlineList::setButtonTag(Button &button) {
if (button.tag) {
void InlineList::setButtonTag(Button &button, const QString &title) {
if (button.tag && button.text == title) {
return;
}
button.userpics = nullptr;
button.count = 0;
button.tag = true;
button.text = title;
button.textWidth = st::reactionInlineTagFont->width(button.text);
}
void InlineList::setButtonCount(Button &button, int count) {
@ -188,8 +196,8 @@ void InlineList::setButtonCount(Button &button, int count) {
button.userpics = nullptr;
button.count = count;
button.tag = false;
button.countText = Lang::FormatCountToShort(count).string;
button.countTextWidth = st::semiboldFont->width(button.countText);
button.text = Lang::FormatCountToShort(count).string;
button.textWidth = st::semiboldFont->width(button.text);
}
void InlineList::setButtonUserpics(
@ -265,10 +273,12 @@ QSize InlineList::countOptimalSize() {
const auto height = padding.top() + size + padding.bottom();
for (auto &button : _buttons) {
const auto width = button.tag
? widthBaseTag
? (widthBaseTag
+ button.textWidth
+ (button.textWidth ? st::reactionInlineSkip : 0))
: button.userpics
? (widthBaseUserpics + userpicsWidth(button))
: (widthBaseCount + button.countTextWidth);
: (widthBaseCount + button.textWidth);
button.geometry.setSize({ width, height });
x += width + between;
}
@ -360,7 +370,7 @@ void InlineList::paint(
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);
p.setFont(tags ? st::reactionInlineTagFont : st::semiboldFont);
for (const auto &button : _buttons) {
if (context.reactionInfo
&& button.animation
@ -460,7 +470,7 @@ void InlineList::paint(
.target = image,
});
}
if (tags || bubbleProgress == 0.) {
if ((tags && !button.textWidth) || bubbleProgress == 0.) {
p.setOpacity(1.);
continue;
}
@ -473,12 +483,19 @@ void InlineList::paint(
button.userpics->image);
} else {
p.setPen(textFg);
const auto textLeft = tags
? (left
- padding.left()
+ st::reactionInlineTagNamePosition.x())
: (left + size + st::reactionInlineSkip);
const auto textTop = geometry.y()
+ ((geometry.height() - st::semiboldFont->height) / 2);
p.drawText(
left + size + st::reactionInlineSkip,
textTop + st::semiboldFont->ascent,
button.countText);
+ (tags
? st::reactionInlineTagNamePosition.y()
: ((geometry.height() - st::semiboldFont->height) / 2));
const auto font = tags
? st::reactionInlineTagFont
: st::semiboldFont;
p.drawText(textLeft, textTop + font->ascent, button.text);
}
if (!bubbleReady) {
p.setOpacity(1.);
@ -569,9 +586,11 @@ QImage InlineList::PrepareTagBg(QColor tagBg, QColor dotBg) {
p.setBrush(tagBg);
p.drawPath(path);
p.setPen(Qt::NoPen);
p.setBrush(dotBg);
p.drawEllipse(dot);
if (dotBg.alpha() > 0) {
p.setPen(Qt::NoPen);
p.setBrush(dotBg);
p.drawEllipse(dot);
}
p.end();

View file

@ -107,7 +107,7 @@ private:
void layout();
void layoutButtons();
void setButtonTag(Button &button);
void setButtonTag(Button &button, const QString &title);
void setButtonCount(Button &button, int count);
void setButtonUserpics(
Button &button,

View file

@ -864,6 +864,8 @@ reactionInlineTagRightRadius: 3px;
reactionInlineTagArrow: 5px;
reactionInlineTagDot: 5px;
reactionInlineTagDotSkip: 2px;
reactionInlineTagFont: font(12px);
reactionInlineTagNamePosition: point(26px, 2px);
reactionInlineBetween: 4px;
reactionInlineInBubbleLeft: -3px;
reactionInlineUserpicsPadding: margins(1px, 1px, 1px, 1px);