Display reactions outside bottom info in groups.

This commit is contained in:
John Preston 2021-12-08 09:28:17 +04:00
parent 3aacd15ef2
commit 535fd8d523
9 changed files with 328 additions and 40 deletions

View file

@ -652,6 +652,8 @@ PRIVATE
history/view/history_view_pinned_section.h
history/view/history_view_pinned_tracker.cpp
history/view/history_view_pinned_tracker.h
history/view/history_view_reactions.cpp
history/view/history_view_reactions.h
history/view/history_view_replies_section.cpp
history/view/history_view_replies_section.h
history/view/history_view_requests_bar.cpp

View file

@ -655,7 +655,7 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
&& chat->groupCall()->fullCount() > 0))
? Flag::CallNotEmpty
: Flag())
| (data.is_noforwards() ? Flag::NoForwards : Flag());
| (data.is_noforwards() ? Flag() : Flag()); AssertIsDebug();
chat->setFlags((chat->flags() & ~flagsMask) | flagsSet);
chat->count = data.vparticipants_count().v;
@ -765,7 +765,7 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
? (data.is_left() ? Flag::Left : Flag())
| (data.is_creator() ? Flag::Creator : Flag())
: Flag())
| (data.is_noforwards() ? Flag::NoForwards : Flag());
| (data.is_noforwards() ? Flag() : Flag()); AssertIsDebug();
channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
channel->setName(

View file

@ -1699,12 +1699,15 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
this,
st::reactionMenu);
auto &reactions = item->history()->owner().reactions();
for (const auto &entry : reactions.list(item->history()->peer)) {
reactionMenu->addAction(entry.emoji, [=] {
item->addReaction(entry.emoji);
});
const auto &list = reactions.list(item->history()->peer);
if (!list.empty()) {
for (const auto &entry : list) {
reactionMenu->addAction(entry.emoji, [=] {
item->addReaction(entry.emoji);
});
}
_menu->addAction("Reaction", std::move(reactionMenu));
}
_menu->addAction("Reaction", std::move(reactionMenu));
}
if (canSendMessages) {
_menu->addAction(tr::lng_context_reply_msg(tr::now), [=] {

View file

@ -323,8 +323,10 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
const auto item = message->message();
result.date = message->dateTime();
result.reactions = item->reactions();
result.chosenReaction = item->chosenReaction();
if (message->embedReactionsInBottomInfo()) {
result.reactions = item->reactions();
result.chosenReaction = item->chosenReaction();
}
if (message->hasOutLayout()) {
result.flags |= Flag::OutLayout;
}

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_message.h"
#include "history/view/media/history_view_media.h"
#include "history/view/media/history_view_web_page.h"
#include "history/view/history_view_reactions.h"
#include "history/view/history_view_group_call_bar.h" // UserpicInRow.
#include "history/view/history_view_view_button.h" // ViewButton.
#include "history/history.h"
@ -247,6 +248,7 @@ Message::Message(
BottomInfoContextFromMessage(this)) {
initLogEntryOriginal();
initPsa();
refreshReactions();
}
Message::~Message() {
@ -319,8 +321,13 @@ QSize Message::performCountOptimalSize() {
updateViewButtonExistence();
updateMediaInBubbleState();
refreshRightBadge();
initTime();
refreshInfoSkipBlock();
const auto displayInfo = needInfoDisplay();
const auto reactionsInBubble = _reactions && displayInfo;
if (_reactions) {
_reactions->initDimensions();
}
if (drawBubble()) {
const auto forwarded = item->Get<HistoryMessageForwarded>();
const auto reply = displayedReply();
@ -345,22 +352,19 @@ QSize Message::performCountOptimalSize() {
// Entry page is always a bubble bottom.
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
if (mediaOnBottom || (mediaDisplayed && _viewButton)) {
if (item->_text.removeSkipBlock()) {
item->_textWidth = -1;
item->_textHeight = 0;
}
} else if (item->_text.updateSkipBlock(skipBlockWidth(), skipBlockHeight())) {
item->_textWidth = -1;
item->_textHeight = 0;
}
maxWidth = plainMaxWidth();
if (context() == Context::Replies && item->isDiscussionPost()) {
maxWidth = std::max(maxWidth, st::msgMaxWidth);
}
minHeight = hasVisibleText() ? item->_text.minHeight() : 0;
if (reactionsInBubble) {
accumulate_max(maxWidth, std::min(
st::msgMaxWidth,
(st::msgPadding.left()
+ _reactions->maxWidth()
+ st::msgPadding.right())));
minHeight += st::mediaInBubbleSkip + _reactions->minHeight();
}
if (!mediaOnBottom) {
minHeight += st::msgPadding.bottom();
if (mediaDisplayed) minHeight += st::mediaInBubbleSkip;
@ -374,9 +378,17 @@ QSize Message::performCountOptimalSize() {
// Parts don't participate in maxWidth() in case of media message.
if (media->enforceBubbleWidth()) {
maxWidth = media->maxWidth();
const auto innerWidth = maxWidth
- st::msgPadding.left()
- st::msgPadding.right();
if (hasVisibleText() && maxWidth < plainMaxWidth()) {
minHeight -= item->_text.minHeight();
minHeight += item->_text.countHeight(maxWidth - st::msgPadding.left() - st::msgPadding.right());
minHeight += item->_text.countHeight(innerWidth);
}
if (reactionsInBubble) {
minHeight -= _reactions->minHeight();
minHeight
+= _reactions->countCurrentSize(innerWidth).height();
}
} else {
accumulate_max(maxWidth, media->maxWidth());
@ -441,6 +453,13 @@ QSize Message::performCountOptimalSize() {
maxWidth = st::msgMinWidth;
minHeight = 0;
}
if (_reactions && !reactionsInBubble) {
// if we have a text bubble we can resize it to fit the keyboard
// but if we have only media we don't do that
if (hasVisibleText()) {
accumulate_max(maxWidth, _reactions->maxWidth());
}
}
if (const auto markup = item->inlineReplyMarkup()) {
if (!markup->inlineKeyboard) {
markup->inlineKeyboard = std::make_unique<ReplyKeyboard>(
@ -518,6 +537,9 @@ void Message::draw(Painter &p, const PaintContext &context) const {
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
const auto displayInfo = needInfoDisplay();
const auto reactionsInBubble = _reactions && displayInfo;
auto mediaSelectionIntervals = (!context.selected() && mediaDisplayed)
? media->getBubbleSelectionIntervals(context.selection)
: std::vector<Ui::BubbleSelectionInterval>();
@ -531,6 +553,9 @@ void Message::draw(Painter &p, const PaintContext &context) const {
if (_viewButton) {
localMediaBottom -= st::mediaInBubbleSkip + _viewButton->height();
}
if (reactionsInBubble) {
localMediaBottom -= st::mediaInBubbleSkip + _reactions->height();
}
if (!mediaOnBottom) {
localMediaBottom -= st::msgPadding.bottom();
}
@ -562,14 +587,23 @@ void Message::draw(Painter &p, const PaintContext &context) const {
auto keyboard = item->inlineReplyKeyboard();
if (keyboard) {
auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
const auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
g.setHeight(g.height() - keyboardHeight);
auto keyboardPosition = QPoint(g.left(), g.top() + g.height() + st::msgBotKbButton.margin);
const auto keyboardPosition = QPoint(g.left(), g.top() + g.height() + st::msgBotKbButton.margin);
p.translate(keyboardPosition);
keyboard->paint(p, context.st, g.width(), context.clip.translated(-keyboardPosition));
p.translate(-keyboardPosition);
}
if (_reactions && !reactionsInBubble) {
const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
g.setHeight(g.height() - reactionsHeight);
const auto reactionsPosition = QPoint(g.left(), g.top() + g.height() + st::mediaInBubbleSkip);
p.translate(reactionsPosition);
_reactions->paint(p, context.st, g.width(), context.clip.translated(-reactionsPosition));
p.translate(-reactionsPosition);
}
if (bubble) {
if (displayFromName()
&& item->displayFrom()
@ -606,7 +640,6 @@ void Message::draw(Painter &p, const PaintContext &context) const {
auto inner = g;
paintCommentsButton(p, inner, context);
const auto needDrawInfo = needInfoDisplay();
auto trect = inner.marginsRemoved(st::msgPadding);
if (_viewButton) {
const auto belowInfo = _viewButton->belowMessageInfo();
@ -627,6 +660,15 @@ void Message::draw(Painter &p, const PaintContext &context) const {
}
}
if (reactionsInBubble) {
const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
trect.setHeight(trect.height() - reactionsHeight);
const auto reactionsPosition = QPoint(trect.left(), trect.top() + trect.height() + st::mediaInBubbleSkip);
p.translate(reactionsPosition);
_reactions->paint(p, context.st, g.width(), context.clip.translated(-reactionsPosition));
p.translate(-reactionsPosition);
}
if (mediaOnBottom) {
trect.setHeight(trect.height() + st::msgPadding.bottom());
}
@ -641,7 +683,7 @@ void Message::draw(Painter &p, const PaintContext &context) const {
if (entry) {
trect.setHeight(trect.height() - entry->height());
}
if (needDrawInfo) {
if (displayInfo) {
trect.setHeight(trect.height()
- (_bottomInfo.height() - st::msgDateFont->height));
}
@ -671,7 +713,7 @@ void Message::draw(Painter &p, const PaintContext &context) const {
entry->draw(p, entryContext);
p.translate(-entryLeft, -entryTop);
}
if (needDrawInfo) {
if (displayInfo) {
const auto bottomSelected = context.selected()
|| (!mediaSelectionIntervals.empty()
&& (mediaSelectionIntervals.back().top
@ -1030,6 +1072,7 @@ PointState Message::pointState(QPoint point) const {
const auto media = this->media();
const auto item = message();
const auto reactionsInBubble = _reactions && needInfoDisplay();
if (drawBubble()) {
if (!g.contains(point)) {
return PointState::Outside;
@ -1052,6 +1095,11 @@ PointState Message::pointState(QPoint point) const {
trect.setHeight(trect.height() - st::mediaInBubbleSkip);
}
}
if (reactionsInBubble) {
const auto reactionsHeight = st::mediaInBubbleSkip
+ _reactions->height();
trect.setHeight(trect.height() - reactionsHeight);
}
if (mediaOnBottom) {
trect.setHeight(trect.height() + st::msgPadding.bottom());
}
@ -1198,6 +1246,7 @@ TextState Message::textState(
return result;
}
const auto reactionsInBubble = _reactions && needInfoDisplay();
auto keyboard = item->inlineReplyKeyboard();
auto keyboardHeight = 0;
if (keyboard) {
@ -1242,6 +1291,11 @@ TextState Message::textState(
trect.setHeight(trect.height() - st::mediaInBubbleSkip);
}
}
if (reactionsInBubble) {
const auto reactionsHeight = _reactions->height()
+ st::mediaInBubbleSkip;
trect.setHeight(trect.height() - reactionsHeight);
}
if (mediaOnBottom) {
trect.setHeight(trect.height()
+ st::msgPadding.bottom()
@ -1828,14 +1882,38 @@ bool Message::isSignedAuthorElided() const {
return _bottomInfo.isSignedAuthorElided();
}
bool Message::embedReactionsInBottomInfo() const {
return data()->history()->peer->isUser();
}
void Message::refreshReactions() {
const auto item = data();
const auto &list = item->reactions();
if (list.empty() || embedReactionsInBottomInfo()) {
_reactions = nullptr;
} else if (!_reactions) {
_reactions = std::make_unique<Reactions>(
ReactionsDataFromMessage(this));
} else {
_reactions->update(ReactionsDataFromMessage(this), width());
}
}
void Message::itemDataChanged() {
const auto wasInfo = _bottomInfo.currentSize();
const auto wasReactions = _reactions
? _reactions->currentSize()
: QSize();
refreshReactions();
_bottomInfo.update(
BottomInfoDataFromMessage(this),
BottomInfoContextFromMessage(this),
width());
const auto nowInfo = _bottomInfo.currentSize();
if (wasInfo != nowInfo) {
const auto nowReactions = _reactions
? _reactions->currentSize()
: QSize();
if (wasInfo != nowInfo || wasReactions != nowReactions) {
history()->owner().requestViewResize(this);
} else {
history()->owner().requestViewRepaint(this);
@ -2305,7 +2383,9 @@ void Message::updateMediaInBubbleState() {
const auto item = message();
const auto media = this->media();
auto mediaHasSomethingBelow = (_viewButton != nullptr);
const auto reactionsInBubble = (_reactions && needInfoDisplay());
auto mediaHasSomethingBelow = (_viewButton != nullptr)
|| reactionsInBubble;
auto mediaHasSomethingAbove = false;
auto getMediaHasSomethingAbove = [&] {
return displayFromName()
@ -2490,10 +2570,13 @@ int Message::resizeContentGetHeight(int newWidth) {
}
}
}
const auto textWidth = qMax(contentWidth - st::msgPadding.left() - st::msgPadding.right(), 1);
const auto displayInfo = needInfoDisplay();
const auto reactionsInBubble = _reactions && displayInfo;
const auto bottomInfoHeight = _bottomInfo.resizeGetHeight(
std::min(
_bottomInfo.optimalSize().width(),
contentWidth - st::msgPadding.left() - st::msgPadding.right() - 2 * st::msgDateDelta.x()));
textWidth - 2 * st::msgDateDelta.x()));
if (bubble) {
auto reply = displayedReply();
@ -2504,6 +2587,10 @@ int Message::resizeContentGetHeight(int newWidth) {
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
if (reactionsInBubble) {
_reactions->resizeGetHeight(textWidth);
}
if (contentWidth == maxWidth()) {
if (mediaDisplayed) {
if (entry) {
@ -2515,7 +2602,6 @@ int Message::resizeContentGetHeight(int newWidth) {
}
} else {
if (hasVisibleText()) {
auto textWidth = qMax(contentWidth - st::msgPadding.left() - st::msgPadding.right(), 1);
if (textWidth != item->_textWidth) {
item->_textWidth = textWidth;
item->_textHeight = item->_text.countHeight(textWidth);
@ -2541,6 +2627,9 @@ int Message::resizeContentGetHeight(int newWidth) {
} else if (entry) {
newHeight += entry->resizeGetHeight(contentWidth);
}
if (reactionsInBubble) {
newHeight += st::mediaInBubbleSkip + _reactions->height();
}
}
if (displayFromName()) {
@ -2564,7 +2653,7 @@ int Message::resizeContentGetHeight(int newWidth) {
reply->resize(contentWidth - st::msgPadding.left() - st::msgPadding.right());
newHeight += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
}
if (needInfoDisplay()) {
if (displayInfo) {
newHeight += (bottomInfoHeight - st::msgDateFont->height);
}
@ -2577,6 +2666,9 @@ int Message::resizeContentGetHeight(int newWidth) {
} else {
newHeight = 0;
}
if (_reactions && !reactionsInBubble) {
newHeight += st::mediaInBubbleSkip + _reactions->resizeGetHeight(contentWidth);
}
if (const auto keyboard = item->inlineReplyKeyboard()) {
const auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
newHeight += keyboardHeight;
@ -2591,9 +2683,6 @@ bool Message::needInfoDisplay() const {
const auto media = this->media();
const auto mediaDisplayed = media ? media->isDisplayed() : false;
const auto entry = logEntryOriginal();
// Entry page is always a bubble bottom.
const auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
return entry
? !entry->customInfoLayout()
: (mediaDisplayed
@ -2615,13 +2704,38 @@ QSize Message::performCountCurrentSize(int newWidth) {
return { newWidth, newHeight };
}
void Message::initTime() const {
void Message::refreshInfoSkipBlock() {
const auto item = message();
if (item->_text.hasSkipBlock()) {
if (item->_text.updateSkipBlock(skipBlockWidth(), skipBlockHeight())) {
const auto media = this->media();
const auto hasTextSkipBlock = [&] {
if (item->_text.isEmpty()) {
return false;
} else if (item->Has<HistoryMessageLogEntryOriginal>()) {
return false;
} else if (media && media->isDisplayed()) {
return false;
} else if (_reactions) {
return false;
}
return true;
}();
const auto skipWidth = skipBlockWidth();
const auto skipHeight = skipBlockHeight();
if (_reactions) {
if (needInfoDisplay()) {
_reactions->updateSkipBlock(skipWidth, skipHeight);
} else {
_reactions->removeSkipBlock();
}
}
if (!hasTextSkipBlock) {
if (item->_text.removeSkipBlock()) {
item->_textWidth = -1;
item->_textHeight = 0;
}
} else if (item->_text.updateSkipBlock(skipWidth, skipHeight)) {
item->_textWidth = -1;
item->_textHeight = 0;
}
}

View file

@ -19,6 +19,7 @@ struct HistoryMessageForwarded;
namespace HistoryView {
class ViewButton;
class Reactions;
class WebPage;
// Special type of Component for the channel actions log.
@ -56,6 +57,8 @@ public:
[[nodiscard]] const HistoryMessageEdited *displayedEditBadge() const;
[[nodiscard]] HistoryMessageEdited *displayedEditBadge();
[[nodiscard]] bool embedReactionsInBottomInfo() const;
int marginTop() const override;
int marginBottom() const override;
void draw(Painter &p, const PaintContext &context) const override;
@ -211,7 +214,7 @@ private:
[[nodiscard]] ClickHandlerPtr fastReplyLink() const;
[[nodiscard]] bool displayPinIcon() const;
void initTime() const;
void refreshInfoSkipBlock();
[[nodiscard]] int plainMaxWidth() const;
[[nodiscard]] int monospaceMaxWidth() const;
@ -225,11 +228,13 @@ private:
void psaTooltipToggled(bool shown) const;
void refreshRightBadge();
void refreshReactions();
mutable ClickHandlerPtr _rightActionLink;
mutable ClickHandlerPtr _fastReplyLink;
mutable std::unique_ptr<CommentsButton> _comments;
mutable std::unique_ptr<ViewButton> _viewButton;
std::unique_ptr<Reactions> _reactions;
mutable std::unique_ptr<CommentsButton> _comments;
Ui::Text::String _rightBadge;
int _bubbleWidthLimit = 0;

View file

@ -0,0 +1,104 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/history_view_reactions.h"
#include "history/view/history_view_message.h"
#include "history/history_message.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h"
namespace HistoryView {
Reactions::Reactions(Data &&data)
: _data(std::move(data))
, _reactions(st::msgMinWidth / 2) {
layout();
}
void Reactions::update(Data &&data, int availableWidth) {
_data = std::move(data);
layout();
if (width() > 0) {
resizeGetHeight(std::min(maxWidth(), availableWidth));
}
}
void Reactions::updateSkipBlock(int width, int height) {
_reactions.updateSkipBlock(width, height);
}
void Reactions::removeSkipBlock() {
_reactions.removeSkipBlock();
}
void Reactions::layout() {
layoutReactionsText();
initDimensions();
}
void Reactions::layoutReactionsText() {
if (_data.reactions.empty()) {
_reactions.clear();
return;
}
auto sorted = ranges::view::all(
_data.reactions
) | ranges::view::transform([](const auto &pair) {
return std::make_pair(pair.first, pair.second);
}) | ranges::to_vector;
ranges::sort(sorted, std::greater<>(), &std::pair<QString, int>::second);
auto text = TextWithEntities();
for (const auto &[string, count] : sorted) {
if (!text.text.isEmpty()) {
text.append(" - ");
}
const auto chosen = (_data.chosenReaction == string);
text.append(string);
if (_data.chosenReaction == string) {
text.append(Ui::Text::Bold(QString::number(count)));
} else {
text.append(QString::number(count));
}
}
_reactions.setMarkedText(
st::msgDateTextStyle,
text,
Ui::NameTextOptions());
}
QSize Reactions::countOptimalSize() {
return QSize(_reactions.maxWidth(), _reactions.minHeight());
}
QSize Reactions::countCurrentSize(int newWidth) {
if (newWidth >= maxWidth()) {
return optimalSize();
}
return { newWidth, _reactions.countHeight(newWidth) };
}
void Reactions::paint(
Painter &p,
const Ui::ChatStyle *st,
int outerWidth,
const QRect &clip) const {
_reactions.draw(p, 0, 0, outerWidth);
}
Reactions::Data ReactionsDataFromMessage(not_null<Message*> message) {
auto result = Reactions::Data();
const auto item = message->message();
result.reactions = item->reactions();
result.chosenReaction = item->chosenReaction();
return result;
}
} // namespace HistoryView

View file

@ -0,0 +1,55 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "history/view/history_view_object.h"
namespace Ui {
class ChatStyle;
} // namespace Ui
namespace HistoryView {
class Message;
class Reactions final : public Object {
public:
struct Data {
base::flat_map<QString, int> reactions;
QString chosenReaction;
};
explicit Reactions(Data &&data);
void update(Data &&data, int availableWidth);
QSize countCurrentSize(int newWidth) override;
void updateSkipBlock(int width, int height);
void removeSkipBlock();
void paint(
Painter &p,
const Ui::ChatStyle *st,
int outerWidth,
const QRect &clip) const;
private:
void layout();
void layoutReactionsText();
QSize countOptimalSize() override;
Data _data;
Ui::Text::String _reactions;
};
[[nodiscard]] Reactions::Data ReactionsDataFromMessage(
not_null<Message*> message);
} // namespace HistoryView

View file

@ -531,6 +531,9 @@ TextForMimeData GroupedMedia::selectedText(
auto GroupedMedia::getBubbleSelectionIntervals(
TextSelection selection) const
-> std::vector<Ui::BubbleSelectionInterval> {
if (_mode != Mode::Column) {
return {};
}
auto result = std::vector<Ui::BubbleSelectionInterval>();
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
const auto &part = _parts[i];