mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-18 23:27:09 +02:00
Implement recent reaction userpics in groups.
This commit is contained in:
parent
2dec1b72f7
commit
f3e84de5fb
7 changed files with 204 additions and 12 deletions
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue