Implement recent reaction userpics in groups.

This commit is contained in:
John Preston 2022-01-13 13:13:08 +03:00
parent 2dec1b72f7
commit f3e84de5fb
7 changed files with 204 additions and 12 deletions

View File

@ -438,19 +438,32 @@ void MessageReactions::add(const QString &reaction) {
if (_chosen == reaction) {
return;
}
const auto history = _item->history();
const auto self = history->session().user();
if (!_chosen.isEmpty()) {
const auto i = _list.find(_chosen);
Assert(i != end(_list));
--i->second;
if (!i->second) {
const auto removed = !i->second;
if (removed) {
_list.erase(i);
}
const auto j = _recent.find(_chosen);
if (j != end(_recent)) {
j->second.erase(ranges::remove(j->second, self), end(j->second));
if (j->second.empty() || removed) {
_recent.erase(j);
}
}
}
_chosen = reaction;
if (!reaction.isEmpty()) {
if (_item->canViewReactions()) {
_recent[reaction].push_back(self);
}
++_list[reaction];
}
auto &owner = _item->history()->owner();
auto &owner = history->owner();
owner.reactions().send(_item, _chosen);
owner.notifyItemDataChange(_item);
}
@ -461,8 +474,10 @@ void MessageReactions::remove() {
void MessageReactions::set(
const QVector<MTPReactionCount> &list,
const QVector<MTPMessageUserReaction> &recent,
bool ignoreChosen) {
if (_item->history()->owner().reactions().sending(_item)) {
auto &owner = _item->history()->owner();
if (owner.reactions().sending(_item)) {
// We'll apply non-stale data from the request response.
return;
}
@ -499,8 +514,24 @@ void MessageReactions::set(
_chosen = QString();
}
}
auto parsed = base::flat_map<
QString,
std::vector<not_null<UserData*>>>();
for (const auto &reaction : recent) {
reaction.match([&](const MTPDmessageUserReaction &data) {
const auto emoji = qs(data.vreaction());
if (_list.contains(emoji)) {
parsed[emoji].push_back(owner.user(data.vuser_id()));
}
});
}
if (_recent != parsed) {
_recent = std::move(parsed);
changed = true;
}
if (changed) {
_item->history()->owner().notifyItemDataChange(_item);
owner.notifyItemDataChange(_item);
}
}
@ -508,6 +539,11 @@ const base::flat_map<QString, int> &MessageReactions::list() const {
return _list;
}
auto MessageReactions::recent() const
-> const base::flat_map<QString, std::vector<not_null<UserData*>>> & {
return _recent;
}
bool MessageReactions::empty() const {
return _list.empty();
}

View File

@ -125,8 +125,13 @@ public:
void add(const QString &reaction);
void remove();
void set(const QVector<MTPReactionCount> &list, bool ignoreChosen);
void set(
const QVector<MTPReactionCount> &list,
const QVector<MTPMessageUserReaction> &recent,
bool ignoreChosen);
[[nodiscard]] const base::flat_map<QString, int> &list() const;
[[nodiscard]] auto recent() const
-> const base::flat_map<QString, std::vector<not_null<UserData*>>> &;
[[nodiscard]] QString chosen() const;
[[nodiscard]] bool empty() const;
@ -135,6 +140,7 @@ private:
QString _chosen;
base::flat_map<QString, int> _list;
base::flat_map<QString, std::vector<not_null<UserData*>>> _recent;
};

View File

@ -834,7 +834,10 @@ void HistoryItem::updateReactions(const MTPMessageReactions *reactions) {
} else if (!_reactions) {
_reactions = std::make_unique<Data::MessageReactions>(this);
}
_reactions->set(data.vresults().v, data.is_min());
_reactions->set(
data.vresults().v,
data.vrecent_reactons().value_or_empty(),
data.is_min());
});
}
@ -847,6 +850,14 @@ const base::flat_map<QString, int> &HistoryItem::reactions() const {
return _reactions ? _reactions->list() : kEmpty;
}
auto HistoryItem::recentReactions() const
-> const base::flat_map<QString, std::vector<not_null<UserData*>>> & {
static const auto kEmpty = base::flat_map<
QString,
std::vector<not_null<UserData*>>>();
return _reactions ? _reactions->recent() : kEmpty;
}
bool HistoryItem::canViewReactions() const {
return (_flags & MessageFlag::CanViewReactions)
&& _reactions

View File

@ -359,6 +359,8 @@ public:
void updateReactions(const MTPMessageReactions *reactions);
void updateReactionsUnknown();
[[nodiscard]] const base::flat_map<QString, int> &reactions() const;
[[nodiscard]] auto recentReactions() const
-> const base::flat_map<QString, std::vector<not_null<UserData*>>> &;
[[nodiscard]] bool canViewReactions() const;
[[nodiscard]] QString chosenReaction() const;
[[nodiscard]] crl::time lastReactionsRefreshTime() const;

View File

@ -12,7 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_message.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_react_animation.h"
#include "history/view/history_view_group_call_bar.h"
#include "data/data_message_reactions.h"
#include "data/data_user.h"
#include "lang/lang_tag.h"
#include "ui/chat/chat_style.h"
#include "styles/style_chat.h"
@ -22,6 +24,7 @@ namespace {
constexpr auto kInNonChosenOpacity = 0.12;
constexpr auto kOutNonChosenOpacity = 0.18;
constexpr auto kMaxRecentUserpics = 3;
[[nodiscard]] QColor AdaptChosenServiceFg(QColor serviceBg) {
serviceBg.setAlpha(std::max(serviceBg.alpha(), 192));
@ -92,7 +95,12 @@ void InlineList::layoutButtons() {
? std::move(*i)
: prepareButtonWithEmoji(emoji));
const auto add = (emoji == _data.chosenReaction) ? 1 : 0;
setButtonCount(buttons.back(), count + add);
const auto j = _data.recent.find(emoji);
if (j != end(_data.recent) && !j->second.empty()) {
setButtonUserpics(buttons.back(), j->second);
} else {
setButtonCount(buttons.back(), count + add);
}
}
_buttons = std::move(buttons);
}
@ -104,14 +112,53 @@ InlineList::Button InlineList::prepareButtonWithEmoji(const QString &emoji) {
}
void InlineList::setButtonCount(Button &button, int count) {
if (button.count == count) {
if (button.count == count && !button.userpics) {
return;
}
button.userpics = nullptr;
button.count = count;
button.countText = Lang::FormatCountToShort(count).string;
button.countTextWidth = st::semiboldFont->width(button.countText);
}
void InlineList::setButtonUserpics(
Button &button,
const std::vector<not_null<UserData*>> &users) {
if (!button.userpics) {
button.userpics = std::make_unique<Userpics>();
}
const auto count = int(users.size());
auto &list = button.userpics->list;
const auto regenerate = [&] {
if (list.size() != count) {
return true;
}
for (auto i = 0; i != count; ++i) {
if (users[i] != list[i].peer) {
return true;
}
}
return false;
}();
if (!regenerate) {
return;
}
auto generated = std::vector<UserpicInRow>();
generated.reserve(count);
for (auto i = 0; i != count; ++i) {
if (i == list.size()) {
list.push_back(UserpicInRow{
users[i]
});
} else if (list[i].peer != users[i]) {
list[i].peer = users[i];
}
}
while (list.size() > count) {
list.pop_back();
}
}
QSize InlineList::countOptimalSize() {
if (_buttons.empty()) {
return _skipBlock;
@ -123,13 +170,26 @@ QSize InlineList::countOptimalSize() {
const auto between = st::reactionInlineBetween;
const auto padding = st::reactionInlinePadding;
const auto size = st::reactionInlineSize;
const auto widthBase = padding.left()
const auto widthBaseCount = padding.left()
+ size
+ st::reactionInlineSkip
+ padding.right();
const auto widthBaseUserpics = padding.left()
+ size
+ st::reactionInlineUserpicsPadding.left()
+ st::reactionInlineUserpicsPadding.right();
const auto userpicsWidth = [](const Button &button) {
const auto count = int(button.userpics->list.size());
const auto single = st::reactionInlineUserpics.size;
const auto shift = st::reactionInlineUserpics.shift;
const auto width = single + (count - 1) * (single - shift);
return width;
};
const auto height = padding.top() + size + padding.bottom();
for (auto &button : _buttons) {
const auto width = widthBase + button.countTextWidth;
const auto width = button.userpics
? (widthBaseUserpics + userpicsWidth(button))
: (widthBaseCount + button.countTextWidth);
button.geometry.setSize({ width, height });
x += width + between;
}
@ -243,7 +303,16 @@ void InlineList::paint(
return _animation->paintGetArea(p, QPoint(), image);
};
}
if (!skipBubble) {
if (skipBubble) {
continue;
}
resolveUserpicsImage(button);
if (button.userpics) {
p.drawImage(
inner.x() + size + st::reactionInlineUserpicsPadding.left(),
geometry.y() + st::reactionInlineUserpicsPadding.top(),
button.userpics->image);
} else {
p.setPen(!inbubble
? (chosen
? QPen(AdaptChosenServiceFg(st->msgServiceBg()->c))
@ -299,6 +368,35 @@ void InlineList::animateSend(
st::reactionInlineImage);
}
void InlineList::resolveUserpicsImage(const Button &button) const {
const auto userpics = button.userpics.get();
const auto regenerate = [&] {
if (!userpics) {
return false;
} else if (userpics->image.isNull()) {
return true;
}
for (auto &entry : userpics->list) {
const auto peer = entry.peer;
auto &view = entry.view;
const auto wasView = view.get();
if (peer->userpicUniqueKey(view) != entry.uniqueKey
|| view.get() != wasView) {
return true;
}
}
return false;
}();
if (!regenerate) {
return;
}
GenerateUserpicsInRow(
userpics->image,
userpics->list,
st::reactionInlineUserpics,
kMaxRecentUserpics);
}
std::unique_ptr<SendAnimation> InlineList::takeSendAnimation() {
return std::move(_animation);
}
@ -311,9 +409,28 @@ void InlineList::continueSendAnimation(
InlineListData InlineListDataFromMessage(not_null<Message*> message) {
using Flag = InlineListData::Flag;
const auto item = message->message();
auto result = InlineListData();
result.reactions = item->reactions();
const auto &recent = item->recentReactions();
const auto showUserpics = [&] {
if (recent.size() != result.reactions.size()) {
return false;
}
auto b = begin(recent);
auto sum = 0;
for (const auto &[emoji, count] : result.reactions) {
sum += count;
if (emoji != b->first
|| count != b->second.size()
|| sum > kMaxRecentUserpics) {
return false;
}
}
return true;
}();
if (showUserpics) {
result.recent = recent;
}
result.chosenReaction = item->chosenReaction();
if (!result.chosenReaction.isEmpty()) {
--result.reactions[result.chosenReaction];

View File

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data {
class Reactions;
class CloudImageView;
} // namespace Data
namespace Ui {
@ -22,6 +23,7 @@ using PaintContext = Ui::ChatPaintContext;
class Message;
struct TextState;
struct SendReactionAnimationArgs;
struct UserpicInRow;
} // namespace HistoryView
namespace HistoryView::Reactions {
@ -37,6 +39,7 @@ struct InlineListData {
using Flags = base::flags<Flag>;
base::flat_map<QString, int> reactions;
base::flat_map<QString, std::vector<not_null<UserData*>>> recent;
QString chosenReaction;
Flags flags = {};
};
@ -74,10 +77,16 @@ public:
void continueSendAnimation(std::unique_ptr<SendAnimation> animation);
private:
struct Userpics {
QImage image;
std::vector<UserpicInRow> list;
bool someNotLoaded = false;
};
struct Button {
QRect geometry;
mutable QImage image;
mutable ClickHandlerPtr link;
std::unique_ptr<Userpics> userpics;
QString emoji;
QString countText;
int count = 0;
@ -88,7 +97,11 @@ private:
void layoutButtons();
void setButtonCount(Button &button, int count);
void setButtonUserpics(
Button &button,
const std::vector<not_null<UserData*>> &users);
[[nodiscard]] Button prepareButtonWithEmoji(const QString &emoji);
void resolveUserpicsImage(const Button &button) const;
QSize countOptimalSize() override;

View File

@ -969,6 +969,13 @@ reactionInlineImage: 28px;
reactionInlineSkip: 3px;
reactionInlineBetween: 4px;
reactionInlineInBubbleLeft: -3px;
reactionInlineUserpicsPadding: margins(1px, 1px, 1px, 1px);
reactionInlineUserpics: GroupCallUserpics {
size: 18px;
shift: 7px;
stroke: 1px;
align: align(left);
}
reactionInfoSize: 15px;
reactionInfoImage: 30px;