Display unread reactions badge in chats list.

This commit is contained in:
John Preston 2022-01-27 15:55:34 +03:00
parent e9c79886d2
commit 8f33d5903d
23 changed files with 377 additions and 307 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 708 B

After

Width:  |  Height:  |  Size: 727 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 977 B

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -492,7 +492,9 @@ void MessageReactions::add(const QString &reaction) {
} }
const auto j = _recent.find(_chosen); const auto j = _recent.find(_chosen);
if (j != end(_recent)) { if (j != end(_recent)) {
j->second.erase(ranges::remove(j->second, self), end(j->second)); j->second.erase(
ranges::remove(j->second, self, &RecentReaction::peer),
end(j->second));
if (j->second.empty() || removed) { if (j->second.empty() || removed) {
_recent.erase(j); _recent.erase(j);
} }
@ -502,7 +504,7 @@ void MessageReactions::add(const QString &reaction) {
if (!reaction.isEmpty()) { if (!reaction.isEmpty()) {
if (_item->canViewReactions()) { if (_item->canViewReactions()) {
auto &list = _recent[reaction]; auto &list = _recent[reaction];
list.insert(begin(list), self); list.insert(begin(list), RecentReaction{ self });
} }
++_list[reaction]; ++_list[reaction];
} }
@ -557,15 +559,16 @@ void MessageReactions::set(
_chosen = QString(); _chosen = QString();
} }
} }
auto parsed = base::flat_map< auto parsed = base::flat_map<QString, std::vector<RecentReaction>>();
QString,
std::vector<not_null<PeerData*>>>();
for (const auto &reaction : recent) { for (const auto &reaction : recent) {
reaction.match([&](const MTPDmessagePeerReaction &data) { reaction.match([&](const MTPDmessagePeerReaction &data) {
const auto emoji = qs(data.vreaction()); const auto emoji = qs(data.vreaction());
if (_list.contains(emoji)) { if (_list.contains(emoji)) {
parsed[emoji].push_back( parsed[emoji].push_back(RecentReaction{
owner.peer(peerFromMTP(data.vpeer_id()))); .peer = owner.peer(peerFromMTP(data.vpeer_id())),
.unread = data.is_unread(),
.big = data.is_big(),
});
} }
}); });
} }
@ -584,7 +587,7 @@ const base::flat_map<QString, int> &MessageReactions::list() const {
} }
auto MessageReactions::recent() const auto MessageReactions::recent() const
-> const base::flat_map<QString, std::vector<not_null<PeerData*>>> & { -> const base::flat_map<QString, std::vector<RecentReaction>> & {
return _recent; return _recent;
} }

View file

@ -125,6 +125,20 @@ private:
}; };
struct RecentReaction {
not_null<PeerData*> peer;
bool unread = false;
bool big = false;
inline friend constexpr bool operator==(
const RecentReaction &a,
const RecentReaction &b) noexcept {
return (a.peer.get() == b.peer.get())
&& (a.unread == b.unread)
&& (a.big == b.big);
}
};
class MessageReactions final { class MessageReactions final {
public: public:
explicit MessageReactions(not_null<HistoryItem*> item); explicit MessageReactions(not_null<HistoryItem*> item);
@ -137,7 +151,7 @@ public:
bool ignoreChosen); bool ignoreChosen);
[[nodiscard]] const base::flat_map<QString, int> &list() const; [[nodiscard]] const base::flat_map<QString, int> &list() const;
[[nodiscard]] auto recent() const [[nodiscard]] auto recent() const
-> const base::flat_map<QString, std::vector<not_null<PeerData*>>> &; -> const base::flat_map<QString, std::vector<RecentReaction>> &;
[[nodiscard]] QString chosen() const; [[nodiscard]] QString chosen() const;
[[nodiscard]] bool empty() const; [[nodiscard]] bool empty() const;
@ -146,7 +160,7 @@ private:
QString _chosen; QString _chosen;
base::flat_map<QString, int> _list; base::flat_map<QString, int> _list;
base::flat_map<QString, std::vector<not_null<PeerData*>>> _recent; base::flat_map<QString, std::vector<RecentReaction>> _recent;
}; };

View file

@ -306,3 +306,6 @@ dialogsMiniPlay: icon{{ "dialogs/dialogs_mini_play", videoPlayIconFg }};
dialogsUnreadMention: icon{{ "dialogs/dialogs_mention", dialogsUnreadFg }}; dialogsUnreadMention: icon{{ "dialogs/dialogs_mention", dialogsUnreadFg }};
dialogsUnreadMentionOver: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgOver }}; dialogsUnreadMentionOver: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgOver }};
dialogsUnreadMentionActive: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgActive }}; dialogsUnreadMentionActive: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgActive }};
dialogsUnreadReaction: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFg }};
dialogsUnreadReactionOver: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFgOver }};
dialogsUnreadReactionActive: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFgActive }};

View file

@ -85,6 +85,7 @@ void PaintNarrowCounter(
bool displayUnreadCounter, bool displayUnreadCounter,
bool displayUnreadMark, bool displayUnreadMark,
bool displayMentionBadge, bool displayMentionBadge,
bool displayReactionBadge,
int unreadCount, int unreadCount,
bool selected, bool selected,
bool active, bool active,
@ -95,7 +96,10 @@ void PaintNarrowCounter(
const auto counter = (unreadCount > 0) const auto counter = (unreadCount > 0)
? QString::number(unreadCount) ? QString::number(unreadCount)
: QString(); : QString();
const auto allowDigits = displayMentionBadge ? 1 : 3; const auto allowDigits = (displayMentionBadge
|| displayReactionBadge)
? 1
: 3;
const auto unreadRight = st::dialogsPadding.x() const auto unreadRight = st::dialogsPadding.x()
+ st::dialogsPhotoSize; + st::dialogsPhotoSize;
const auto unreadTop = st::dialogsPadding.y() const auto unreadTop = st::dialogsPadding.y()
@ -115,7 +119,7 @@ void PaintNarrowCounter(
allowDigits); allowDigits);
skipBeforeMention += badge.width() + st.padding; skipBeforeMention += badge.width() + st.padding;
} }
if (displayMentionBadge) { if (displayMentionBadge || displayReactionBadge) {
const auto counter = QString(); const auto counter = QString();
const auto unreadRight = st::dialogsPadding.x() const auto unreadRight = st::dialogsPadding.x()
+ st::dialogsPhotoSize + st::dialogsPhotoSize
@ -136,11 +140,17 @@ void PaintNarrowCounter(
unreadRight, unreadRight,
unreadTop, unreadTop,
st); st);
(st.active (displayMentionBadge
? st::dialogsUnreadMentionActive ? (st.active
: st.selected ? st::dialogsUnreadMentionActive
? st::dialogsUnreadMentionOver : st.selected
: st::dialogsUnreadMention).paintInCenter(p, badge); ? st::dialogsUnreadMentionOver
: st::dialogsUnreadMention)
: (st.active
? st::dialogsUnreadReactionActive
: st.selected
? st::dialogsUnreadReactionOver
: st::dialogsUnreadReaction)).paintInCenter(p, badge);
} }
} }
@ -152,6 +162,7 @@ int PaintWideCounter(
bool displayUnreadCounter, bool displayUnreadCounter,
bool displayUnreadMark, bool displayUnreadMark,
bool displayMentionBadge, bool displayMentionBadge,
bool displayReactionBadge,
bool displayPinnedIcon, bool displayPinnedIcon,
int unreadCount, int unreadCount,
bool active, bool active,
@ -199,7 +210,7 @@ int PaintWideCounter(
hadOneBadge = true; hadOneBadge = true;
} }
if (displayMentionBadge) { if (displayMentionBadge || displayReactionBadge) {
const auto counter = QString(); const auto counter = QString();
const auto unreadRight = fullWidth const auto unreadRight = fullWidth
- st::dialogsPadding.x() - st::dialogsPadding.x()
@ -221,11 +232,17 @@ int PaintWideCounter(
unreadRight, unreadRight,
unreadTop, unreadTop,
st); st);
(st.active (displayMentionBadge
? st::dialogsUnreadMentionActive ? (st.active
: st.selected ? st::dialogsUnreadMentionActive
? st::dialogsUnreadMentionOver : st.selected
: st::dialogsUnreadMention).paintInCenter(p, badge); ? st::dialogsUnreadMentionOver
: st::dialogsUnreadMention)
: (st.active
? st::dialogsUnreadReactionActive
: st.selected
? st::dialogsUnreadReactionOver
: st::dialogsUnreadReaction)).paintInCenter(p, badge);
availableWidth -= badge.width() availableWidth -= badge.width()
+ st.padding + st.padding
+ (hadOneBadge ? st::dialogsUnreadPadding : 0); + (hadOneBadge ? st::dialogsUnreadPadding : 0);
@ -779,8 +796,10 @@ void RowPainter::paint(
: QDateTime(); : QDateTime();
}(); }();
const auto displayMentionBadge = history const auto displayMentionBadge = history
? history->unreadMentions().has() && history->unreadMentions().has();
: false; const auto displayReactionBadge = !displayMentionBadge
&& history
&& history->unreadReactions().has();
const auto displayUnreadCounter = [&] { const auto displayUnreadCounter = [&] {
if (displayMentionBadge if (displayMentionBadge
&& unreadCount == 1 && unreadCount == 1
@ -796,6 +815,7 @@ void RowPainter::paint(
&& unreadMark; && unreadMark;
const auto displayPinnedIcon = !displayUnreadCounter const auto displayPinnedIcon = !displayUnreadCounter
&& !displayMentionBadge && !displayMentionBadge
&& !displayReactionBadge
&& !displayUnreadMark && !displayUnreadMark
&& entry->isPinnedDialog(filterId) && entry->isPinnedDialog(filterId)
&& (filterId || !entry->fixedOnTopIndex()); && (filterId || !entry->fixedOnTopIndex());
@ -824,6 +844,7 @@ void RowPainter::paint(
displayUnreadCounter, displayUnreadCounter,
displayUnreadMark, displayUnreadMark,
displayMentionBadge, displayMentionBadge,
displayReactionBadge,
displayPinnedIcon, displayPinnedIcon,
unreadCount, unreadCount,
active, active,
@ -868,6 +889,7 @@ void RowPainter::paint(
displayUnreadCounter, displayUnreadCounter,
displayUnreadMark, displayUnreadMark,
displayMentionBadge, displayMentionBadge,
displayReactionBadge,
unreadCount, unreadCount,
selected, selected,
active, active,
@ -943,6 +965,9 @@ void RowPainter::paint(
const auto mentionMuted = (history->folder() != nullptr); const auto mentionMuted = (history->folder() != nullptr);
const auto displayMentionBadge = displayUnreadInfo const auto displayMentionBadge = displayUnreadInfo
&& history->unreadMentions().has(); && history->unreadMentions().has();
const auto displayReactionBadge = displayUnreadInfo
&& !displayMentionBadge
&& history->unreadReactions().has();
const auto displayUnreadCounter = (unreadCount > 0); const auto displayUnreadCounter = (unreadCount > 0);
const auto displayUnreadMark = !displayUnreadCounter const auto displayUnreadMark = !displayUnreadCounter
&& !displayMentionBadge && !displayMentionBadge
@ -961,6 +986,7 @@ void RowPainter::paint(
displayUnreadCounter, displayUnreadCounter,
displayUnreadMark, displayUnreadMark,
displayMentionBadge, displayMentionBadge,
displayReactionBadge,
displayPinnedIcon, displayPinnedIcon,
unreadCount, unreadCount,
active, active,
@ -987,6 +1013,7 @@ void RowPainter::paint(
displayUnreadCounter, displayUnreadCounter,
displayUnreadMark, displayUnreadMark,
displayMentionBadge, displayMentionBadge,
displayReactionBadge,
unreadCount, unreadCount,
selected, selected,
active, active,

View file

@ -693,7 +693,7 @@ not_null<HistoryItem*> History::addNewLocalMessage(
} }
void History::setUnreadThingsKnown() { void History::setUnreadThingsKnown() {
_flags &= ~Flag::UnreadThingsKnown; _flags |= Flag::UnreadThingsKnown;
} }
HistoryUnreadThings::Proxy History::unreadMentions() { HistoryUnreadThings::Proxy History::unreadMentions() {

View file

@ -848,10 +848,6 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
mouseActionUpdate(); mouseActionUpdate();
} }
const auto guard = gsl::finally([&] {
_userpicsCache.clear();
});
Painter p(this); Painter p(this);
auto clip = e->rect(); auto clip = e->rect();
@ -872,7 +868,6 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
const auto now = crl::now(); const auto now = crl::now();
const auto historyDisplayedEmpty = _history->isDisplayedEmpty() const auto historyDisplayedEmpty = _history->isDisplayedEmpty()
&& (!_migrated || _migrated->isDisplayedEmpty()); && (!_migrated || _migrated->isDisplayedEmpty());
bool noHistoryDisplayed = historyDisplayedEmpty;
if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) { if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) {
const auto st = context.st; const auto st = context.st;
const auto stm = &st->messageStyle(false, false); const auto stm = &st->messageStyle(false, false);
@ -899,254 +894,251 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
_emptyPainter = nullptr; _emptyPainter = nullptr;
} }
_reactionsManager->startEffectsCollection(); const auto mtop = migratedTop();
if (!noHistoryDisplayed) { const auto htop = historyTop();
auto readMentions = base::flat_set<not_null<HistoryItem*>>(); if (historyDisplayedEmpty || (mtop < 0 && htop < 0)) {
return;
}
adjustCurrent(clip.top()); auto readTill = (HistoryItem*)nullptr;
auto readContents = base::flat_set<not_null<HistoryItem*>>();
auto drawToY = clip.y() + clip.height(); const auto guard = gsl::finally([&] {
if (readTill && _widget->doWeReadServerHistory()) {
auto selfromy = itemTop(_dragSelFrom); session().data().histories().readInboxTill(readTill);
auto seltoy = itemTop(_dragSelTo);
if (selfromy < 0 || seltoy < 0) {
selfromy = seltoy = -1;
} else {
seltoy += _dragSelTo->height();
} }
if (!readContents.empty() && _widget->doWeReadMentions()) {
session().api().markContentsRead(readContents);
}
_userpicsCache.clear();
});
auto mtop = migratedTop(); const auto processPainted = [&](
auto htop = historyTop(); not_null<Element*> view,
auto hdrawtop = historyDrawTop(); int top,
if (mtop >= 0) { int height) {
auto iBlock = (_curHistory == _migrated ? _curBlock : (_migrated->blocks.size() - 1)); const auto item = view->data();
auto block = _migrated->blocks[iBlock].get(); const auto isSponsored = item->isSponsored();
auto iItem = (_curHistory == _migrated ? _curItem : (block->messages.size() - 1)); const auto isUnread = !item->out()
auto view = block->messages[iItem].get(); && item->unread()
auto item = view->data(); && (item->history() == _history);
const auto withReaction = item->hasUnreadReaction();
const auto yShown = [&](int y) {
return (_visibleAreaBottom >= y && _visibleAreaTop <= y);
};
const auto markShown = isSponsored
? view->markSponsoredViewed(_visibleAreaBottom - top)
: withReaction
? yShown(top + context.reactionInfo->position.y())
: isUnread
? yShown(top + height)
: yShown(top + height / 2);
if (markShown) {
if (isSponsored) {
session().data().sponsoredMessages().view(
item->fullId());
} else if (isUnread) {
readTill = item;
}
if (item->hasViews()) {
session().api().views().scheduleIncrement(item);
}
if (withReaction) {
readContents.insert(item);
} else if (item->isUnreadMention()
&& !item->isUnreadMedia()) {
readContents.insert(item);
_widget->enqueueMessageHighlight(view);
}
}
session().data().reactions().poll(item, now);
_reactionsManager->recordCurrentReactionEffect(
item->fullId(),
QPoint(0, top));
};
auto top = mtop + block->y() + view->y(); adjustCurrent(clip.top());
context.translate(0, -top);
p.translate(0, top); const auto drawToY = clip.y() + clip.height();
if (context.clip.y() < view->height()) while (top < drawToY) {
context.reactionEffects auto selfromy = itemTop(_dragSelFrom);
= _reactionsManager->currentReactionEffect(); auto seltoy = itemTop(_dragSelTo);
if (selfromy < 0 || seltoy < 0) {
selfromy = seltoy = -1;
} else {
seltoy += _dragSelTo->height();
}
const auto hdrawtop = historyDrawTop();
if (mtop >= 0) {
auto iBlock = (_curHistory == _migrated ? _curBlock : (_migrated->blocks.size() - 1));
auto block = _migrated->blocks[iBlock].get();
auto iItem = (_curHistory == _migrated ? _curItem : (block->messages.size() - 1));
auto view = block->messages[iItem].get();
auto top = mtop + block->y() + view->y();
context.translate(0, -top);
p.translate(0, top);
if (context.clip.y() < view->height()) while (top < drawToY) {
const auto height = view->height();
context.reactionInfo
= _reactionsManager->currentReactionPaintInfo();
context.outbg = view->hasOutLayout();
context.selection = itemRenderSelection(
view,
selfromy - mtop,
seltoy - mtop);
view->draw(p, context);
processPainted(view, top, height);
top += height;
context.translate(0, -height);
p.translate(0, height);
++iItem;
if (iItem == block->messages.size()) {
iItem = 0;
++iBlock;
if (iBlock == _migrated->blocks.size()) {
break;
}
block = _migrated->blocks[iBlock].get();
}
view = block->messages[iItem].get();
}
context.translate(0, top);
p.translate(0, -top);
}
if (htop >= 0) {
auto iBlock = (_curHistory == _history ? _curBlock : 0);
auto block = _history->blocks[iBlock].get();
auto iItem = (_curHistory == _history ? _curItem : 0);
auto view = block->messages[iItem].get();
auto top = htop + block->y() + view->y();
context.clip = clip.intersected(
QRect(0, hdrawtop, width(), clip.top() + clip.height()));
context.translate(0, -top);
p.translate(0, top);
while (top < drawToY) {
const auto height = view->height();
if (context.clip.y() < height && hdrawtop < top + height) {
context.reactionInfo
= _reactionsManager->currentReactionPaintInfo();
context.outbg = view->hasOutLayout(); context.outbg = view->hasOutLayout();
context.selection = itemRenderSelection( context.selection = itemRenderSelection(
view, view,
selfromy - mtop, selfromy - htop,
seltoy - mtop); seltoy - htop);
view->draw(p, context); view->draw(p, context);
_reactionsManager->recordCurrentReactionEffect( processPainted(view, top, height);
item->fullId(),
QPoint(0, top));
const auto height = view->height();
const auto middle = top + height / 2;
if (_visibleAreaBottom >= middle
&& _visibleAreaTop <= middle) {
if (item->hasViews()) {
session().api().views().scheduleIncrement(item);
}
if (item->isUnreadMention() && !item->isUnreadMedia()) {
readMentions.insert(item);
_widget->enqueueMessageHighlight(view);
}
session().data().reactions().poll(item, now);
}
top += height;
context.translate(0, -height);
p.translate(0, height);
++iItem;
if (iItem == block->messages.size()) {
iItem = 0;
++iBlock;
if (iBlock == _migrated->blocks.size()) {
break;
}
block = _migrated->blocks[iBlock].get();
}
view = block->messages[iItem].get();
item = view->data();
} }
context.translate(0, top); top += height;
p.translate(0, -top); context.translate(0, -height);
} p.translate(0, height);
if (htop >= 0) {
auto iBlock = (_curHistory == _history ? _curBlock : 0);
auto block = _history->blocks[iBlock].get();
auto iItem = (_curHistory == _history ? _curItem : 0);
auto view = block->messages[iItem].get();
auto item = view->data();
auto readTill = (HistoryItem*)nullptr;
auto top = htop + block->y() + view->y();
context.clip = clip.intersected(
QRect(0, hdrawtop, width(), clip.top() + clip.height()));
context.translate(0, -top);
p.translate(0, top);
while (top < drawToY) {
const auto height = view->height();
if (context.clip.y() < height && hdrawtop < top + height) {
context.reactionEffects
= _reactionsManager->currentReactionEffect();
context.outbg = view->hasOutLayout();
context.selection = itemRenderSelection(
view,
selfromy - htop,
seltoy - htop);
view->draw(p, context);
_reactionsManager->recordCurrentReactionEffect(
item->fullId(),
QPoint(0, top));
const auto middle = top + height / 2; ++iItem;
const auto bottom = top + height; if (iItem == block->messages.size()) {
if (_visibleAreaBottom >= bottom) { iItem = 0;
if (!item->out() && item->unread()) { ++iBlock;
readTill = item; if (iBlock == _history->blocks.size()) {
} break;
}
if (item->isSponsored()
&& view->markSponsoredViewed(
_visibleAreaBottom - top)) {
session().data().sponsoredMessages().view(
item->fullId());
}
if (_visibleAreaBottom >= middle
&& _visibleAreaTop <= middle) {
if (item->hasViews()) {
session().api().views().scheduleIncrement(item);
}
if (item->isUnreadMention()
&& !item->isUnreadMedia()) {
readMentions.insert(item);
_widget->enqueueMessageHighlight(view);
}
}
session().data().reactions().poll(item, now);
} }
top += height; block = _history->blocks[iBlock].get();
context.translate(0, -height);
p.translate(0, height);
++iItem;
if (iItem == block->messages.size()) {
iItem = 0;
++iBlock;
if (iBlock == _history->blocks.size()) {
break;
}
block = _history->blocks[iBlock].get();
}
view = block->messages[iItem].get();
item = view->data();
}
context.translate(0, top);
p.translate(0, -top);
if (readTill && _widget->doWeReadServerHistory()) {
session().data().histories().readInboxTill(readTill);
} }
view = block->messages[iItem].get();
} }
context.translate(0, top);
if (!readMentions.empty() && _widget->doWeReadMentions()) { p.translate(0, -top);
session().api().markContentsRead(readMentions);
}
if (mtop >= 0 || htop >= 0) {
enumerateUserpics([&](not_null<Element*> view, int userpicTop) {
// stop the enumeration if the userpic is below the painted rect
if (userpicTop >= clip.top() + clip.height()) {
return false;
}
// paint the userpic if it intersects the painted rect
if (userpicTop + st::msgPhotoSize > clip.top()) {
if (const auto from = view->data()->displayFrom()) {
from->paintUserpicLeft(
p,
_userpics[from],
st::historyPhotoLeft,
userpicTop,
width(),
st::msgPhotoSize);
} else if (const auto info = view->data()->hiddenSenderInfo()) {
info->userpic.paint(
p,
st::historyPhotoLeft,
userpicTop,
width(),
st::msgPhotoSize);
} else {
Unexpected("Corrupt forwarded information in message.");
}
}
return true;
});
int dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
//QDate lastDate;
//if (!_history->isEmpty()) {
// lastDate = _history->blocks.back()->messages.back()->data()->date.date();
//}
//// if item top is before this value always show date as a floating date
//int showFloatingBefore = height() - 2 * (_visibleAreaBottom - _visibleAreaTop) - dateHeight;
auto scrollDateOpacity = _scrollDateOpacity.value(_scrollDateShown ? 1. : 0.);
enumerateDates([&](not_null<Element*> view, int itemtop, int dateTop) {
// stop the enumeration if the date is above the painted rect
if (dateTop + dateHeight <= clip.top()) {
return false;
}
const auto displayDate = view->displayDate();
auto dateInPlace = displayDate;
if (dateInPlace) {
const auto correctDateTop = itemtop + st::msgServiceMargin.top();
dateInPlace = (dateTop < correctDateTop + dateHeight);
}
//bool noFloatingDate = (item->date.date() == lastDate && displayDate);
//if (noFloatingDate) {
// if (itemtop < showFloatingBefore) {
// noFloatingDate = false;
// }
//}
// paint the date if it intersects the painted rect
if (dateTop < clip.top() + clip.height()) {
auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity;
if (opacity > 0.) {
p.setOpacity(opacity);
const auto dateY = false // noFloatingDate
? itemtop
: (dateTop - st::msgServiceMargin.top());
if (const auto date = view->Get<HistoryView::DateBadge>()) {
date->paint(p, context.st, dateY, _contentWidth, _isChatWide);
} else {
HistoryView::ServiceMessagePainter::PaintDate(
p,
context.st,
view->dateTime(),
dateY,
_contentWidth,
_isChatWide);
}
}
}
return true;
});
p.setOpacity(1.);
_reactionsManager->paint(p, context);
p.translate(0, _historyPaddingTop);
_emojiInteractions->paint(p);
}
} }
enumerateUserpics([&](not_null<Element*> view, int userpicTop) {
// stop the enumeration if the userpic is below the painted rect
if (userpicTop >= clip.top() + clip.height()) {
return false;
}
// paint the userpic if it intersects the painted rect
if (userpicTop + st::msgPhotoSize > clip.top()) {
if (const auto from = view->data()->displayFrom()) {
from->paintUserpicLeft(
p,
_userpics[from],
st::historyPhotoLeft,
userpicTop,
width(),
st::msgPhotoSize);
} else if (const auto info = view->data()->hiddenSenderInfo()) {
info->userpic.paint(
p,
st::historyPhotoLeft,
userpicTop,
width(),
st::msgPhotoSize);
} else {
Unexpected("Corrupt forwarded information in message.");
}
}
return true;
});
const auto dateHeight = st::msgServicePadding.bottom()
+ st::msgServiceFont->height
+ st::msgServicePadding.top();
//QDate lastDate;
//if (!_history->isEmpty()) {
// lastDate = _history->blocks.back()->messages.back()->data()->date.date();
//}
//// if item top is before this value always show date as a floating date
//int showFloatingBefore = height() - 2 * (_visibleAreaBottom - _visibleAreaTop) - dateHeight;
auto scrollDateOpacity = _scrollDateOpacity.value(_scrollDateShown ? 1. : 0.);
enumerateDates([&](not_null<Element*> view, int itemtop, int dateTop) {
// stop the enumeration if the date is above the painted rect
if (dateTop + dateHeight <= clip.top()) {
return false;
}
const auto displayDate = view->displayDate();
auto dateInPlace = displayDate;
if (dateInPlace) {
const auto correctDateTop = itemtop + st::msgServiceMargin.top();
dateInPlace = (dateTop < correctDateTop + dateHeight);
}
//bool noFloatingDate = (item->date.date() == lastDate && displayDate);
//if (noFloatingDate) {
// if (itemtop < showFloatingBefore) {
// noFloatingDate = false;
// }
//}
// paint the date if it intersects the painted rect
if (dateTop < clip.top() + clip.height()) {
auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity;
if (opacity > 0.) {
p.setOpacity(opacity);
const auto dateY = false // noFloatingDate
? itemtop
: (dateTop - st::msgServiceMargin.top());
if (const auto date = view->Get<HistoryView::DateBadge>()) {
date->paint(p, context.st, dateY, _contentWidth, _isChatWide);
} else {
HistoryView::ServiceMessagePainter::PaintDate(
p,
context.st,
view->dateTime(),
dateY,
_contentWidth,
_isChatWide);
}
}
}
return true;
});
p.setOpacity(1.);
_reactionsManager->paint(p, context);
p.translate(0, _historyPaddingTop);
_emojiInteractions->paint(p);
} }
bool HistoryInner::eventHook(QEvent *e) { bool HistoryInner::eventHook(QEvent *e) {

View file

@ -337,6 +337,12 @@ bool HistoryItem::isUnreadMention() const {
} }
bool HistoryItem::hasUnreadReaction() const { bool HistoryItem::hasUnreadReaction() const {
const auto &recent = recentReactions();
for (const auto &[emoji, list] : recent) {
if (ranges::contains(list, true, &Data::RecentReaction::unread)) {
return true;
}
}
return false; return false;
} }
@ -901,10 +907,10 @@ const base::flat_map<QString, int> &HistoryItem::reactions() const {
} }
auto HistoryItem::recentReactions() const auto HistoryItem::recentReactions() const
-> const base::flat_map<QString, std::vector<not_null<PeerData*>>> & { -> const base::flat_map<QString, std::vector<Data::RecentReaction>> & {
static const auto kEmpty = base::flat_map< static const auto kEmpty = base::flat_map<
QString, QString,
std::vector<not_null<PeerData*>>>(); std::vector<Data::RecentReaction>>();
return _reactions ? _reactions->recent() : kEmpty; return _reactions ? _reactions->recent() : kEmpty;
} }

View file

@ -41,6 +41,7 @@ struct RippleAnimation;
namespace Data { namespace Data {
struct MessagePosition; struct MessagePosition;
struct RecentReaction;
class Media; class Media;
class MessageReactions; class MessageReactions;
} // namespace Data } // namespace Data
@ -367,7 +368,9 @@ public:
void updateReactionsUnknown(); void updateReactionsUnknown();
[[nodiscard]] const base::flat_map<QString, int> &reactions() const; [[nodiscard]] const base::flat_map<QString, int> &reactions() const;
[[nodiscard]] auto recentReactions() const [[nodiscard]] auto recentReactions() const
-> const base::flat_map<QString, std::vector<not_null<PeerData*>>> &; -> const base::flat_map<
QString,
std::vector<Data::RecentReaction>> &;
[[nodiscard]] bool canViewReactions() const; [[nodiscard]] bool canViewReactions() const;
[[nodiscard]] QString chosenReaction() const; [[nodiscard]] QString chosenReaction() const;
[[nodiscard]] QString lookupHisReaction() const; [[nodiscard]] QString lookupHisReaction() const;

View file

@ -108,7 +108,8 @@ public:
known) known)
, _history(history) , _history(history)
, _data(data) , _data(data)
, _type(type) { , _type(type)
, _known(known) {
} }
void setCount(int count); void setCount(int count);

View file

@ -320,7 +320,7 @@ void BottomInfo::paintReactions(
? _reactionAnimation->playingAroundEmoji() ? _reactionAnimation->playingAroundEmoji()
: QString(); : QString();
if (_reactionAnimation if (_reactionAnimation
&& context.reactionEffects && context.reactionInfo
&& animated.isEmpty()) { && animated.isEmpty()) {
_reactionAnimation = nullptr; _reactionAnimation = nullptr;
} }
@ -354,7 +354,7 @@ void BottomInfo::paintReactions(
p.drawImage(image.topLeft(), reaction.image); p.drawImage(image.topLeft(), reaction.image);
} }
if (animating) { if (animating) {
context.reactionEffects->paint = [=](QPainter &p) { context.reactionInfo->effectPaint = [=](QPainter &p) {
return _reactionAnimation->paintGetArea(p, origin, image); return _reactionAnimation->paintGetArea(p, origin, image);
}; };
} }

View file

@ -1746,8 +1746,8 @@ void ListWidget::paintEvent(QPaintEvent *e) {
p.translate(0, top); p.translate(0, top);
for (auto i = from; i != to; ++i) { for (auto i = from; i != to; ++i) {
const auto view = *i; const auto view = *i;
context.reactionEffects context.reactionInfo
= _reactionsManager->currentReactionEffect(); = _reactionsManager->currentReactionPaintInfo();
context.outbg = view->hasOutLayout(); context.outbg = view->hasOutLayout();
context.selection = itemRenderSelection(view); context.selection = itemRenderSelection(view);
view->draw(p, context); view->draw(p, context);

View file

@ -729,8 +729,8 @@ void Message::draw(Painter &p, const PaintContext &context) const {
const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip); const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip);
p.translate(reactionsPosition); p.translate(reactionsPosition);
_reactions->paint(p, context, g.width(), context.clip.translated(-reactionsPosition)); _reactions->paint(p, context, g.width(), context.clip.translated(-reactionsPosition));
if (context.reactionEffects && context.reactionEffects->paint) { if (context.reactionInfo) {
context.reactionEffects->offset += reactionsPosition; context.reactionInfo->position = reactionsPosition;
} }
p.translate(-reactionsPosition); p.translate(-reactionsPosition);
} }
@ -784,8 +784,8 @@ void Message::draw(Painter &p, const PaintContext &context) const {
const auto reactionsPosition = QPoint(trect.left(), trect.top() + trect.height() + reactionsTop); const auto reactionsPosition = QPoint(trect.left(), trect.top() + trect.height() + reactionsTop);
p.translate(reactionsPosition); p.translate(reactionsPosition);
_reactions->paint(p, context, g.width(), context.clip.translated(-reactionsPosition)); _reactions->paint(p, context, g.width(), context.clip.translated(-reactionsPosition));
if (context.reactionEffects && context.reactionEffects->paint) { if (context.reactionInfo) {
context.reactionEffects->offset += reactionsPosition; context.reactionInfo->position = reactionsPosition;
} }
p.translate(-reactionsPosition); p.translate(-reactionsPosition);
} }
@ -842,10 +842,12 @@ void Message::draw(Painter &p, const PaintContext &context) const {
media->draw(p, context.translated( media->draw(p, context.translated(
-mediaPosition -mediaPosition
).withSelection(skipTextSelection(context.selection))); ).withSelection(skipTextSelection(context.selection)));
if (context.reactionEffects if (context.reactionInfo && !displayInfo && !_reactions) {
&& context.reactionEffects->paint const auto add = QPoint(0, mediaHeight);
&& !_reactions) { context.reactionInfo->position = mediaPosition + add;
context.reactionEffects->offset += mediaPosition; if (context.reactionInfo->effectPaint) {
context.reactionInfo->effectOffset -= add;
}
} }
p.translate(-mediaPosition); p.translate(-mediaPosition);
} }
@ -876,6 +878,13 @@ void Message::draw(Painter &p, const PaintContext &context) const {
inner.top() + inner.height(), inner.top() + inner.height(),
2 * inner.left() + inner.width(), 2 * inner.left() + inner.width(),
InfoDisplayType::Default); InfoDisplayType::Default);
if (context.reactionInfo && !_reactions) {
const auto add = QPoint(0, inner.top() + inner.height());
context.reactionInfo->position = add;
if (context.reactionInfo->effectPaint) {
context.reactionInfo->effectOffset -= add;
}
}
if (_comments) { if (_comments) {
const auto o = p.opacity(); const auto o = p.opacity();
p.setOpacity(0.3); p.setOpacity(0.3);
@ -901,8 +910,12 @@ void Message::draw(Painter &p, const PaintContext &context) const {
media->draw(p, context.translated( media->draw(p, context.translated(
-g.topLeft() -g.topLeft()
).withSelection(skipTextSelection(context.selection))); ).withSelection(skipTextSelection(context.selection)));
if (context.reactionEffects && context.reactionEffects->paint && !_reactions) { if (context.reactionInfo && !_reactions) {
context.reactionEffects->offset += g.topLeft(); const auto add = QPoint(0, g.height());
context.reactionInfo->position = g.topLeft() + add;
if (context.reactionInfo->effectPaint) {
context.reactionInfo->effectOffset -= add;
}
} }
p.translate(-g.topLeft()); p.translate(-g.topLeft());
} }

View file

@ -781,9 +781,9 @@ void Manager::paint(Painter &p, const PaintContext &context) {
} }
for (const auto &[id, effect] : _collectedEffects) { for (const auto &[id, effect] : _collectedEffects) {
const auto offset = effect.offset; const auto offset = effect.effectOffset;
p.translate(offset); p.translate(offset);
_activeEffectAreas[id] = effect.paint(p).translated(offset); _activeEffectAreas[id] = effect.effectPaint(p).translated(offset);
p.translate(-offset); p.translate(-offset);
} }
_collectedEffects.clear(); _collectedEffects.clear();
@ -1543,17 +1543,19 @@ std::optional<QRect> Manager::lookupEffectArea(FullMsgId itemId) const {
void Manager::startEffectsCollection() { void Manager::startEffectsCollection() {
_collectedEffects.clear(); _collectedEffects.clear();
_currentEffect = {}; _currentReactionInfo = {};
} }
not_null<Ui::ReactionEffectPainter*> Manager::currentReactionEffect() { auto Manager::currentReactionPaintInfo()
return &_currentEffect; -> not_null<Ui::ReactionPaintInfo*> {
return &_currentReactionInfo;
} }
void Manager::recordCurrentReactionEffect(FullMsgId itemId, QPoint origin) { void Manager::recordCurrentReactionEffect(FullMsgId itemId, QPoint origin) {
if (_currentEffect.paint) { if (_currentReactionInfo.effectPaint) {
_currentEffect.offset += origin; _currentReactionInfo.effectOffset += origin
_collectedEffects[itemId] = base::take(_currentEffect); + _currentReactionInfo.position;
_collectedEffects[itemId] = base::take(_currentReactionInfo);
} else if (!_collectedEffects.empty()) { } else if (!_collectedEffects.empty()) {
_collectedEffects.remove(itemId); _collectedEffects.remove(itemId);
} }

View file

@ -173,8 +173,8 @@ public:
[[nodiscard]] std::optional<QRect> lookupEffectArea( [[nodiscard]] std::optional<QRect> lookupEffectArea(
FullMsgId itemId) const; FullMsgId itemId) const;
void startEffectsCollection(); void startEffectsCollection();
[[nodiscard]] auto currentReactionEffect() [[nodiscard]] auto currentReactionPaintInfo()
-> not_null<Ui::ReactionEffectPainter*>; -> not_null<Ui::ReactionPaintInfo*>;
void recordCurrentReactionEffect(FullMsgId itemId, QPoint origin); void recordCurrentReactionEffect(FullMsgId itemId, QPoint origin);
bool showContextMenu( bool showContextMenu(
@ -354,8 +354,8 @@ private:
base::flat_map<FullMsgId, QRect> _activeEffectAreas; base::flat_map<FullMsgId, QRect> _activeEffectAreas;
Ui::ReactionEffectPainter _currentEffect; Ui::ReactionPaintInfo _currentReactionInfo;
base::flat_map<FullMsgId, Ui::ReactionEffectPainter> _collectedEffects; base::flat_map<FullMsgId, Ui::ReactionPaintInfo> _collectedEffects;
base::unique_qptr<Ui::PopupMenu> _menu; base::unique_qptr<Ui::PopupMenu> _menu;
rpl::event_stream<QString> _faveRequests; rpl::event_stream<QString> _faveRequests;

View file

@ -274,11 +274,11 @@ void InlineList::paint(
const auto size = st::reactionInlineSize; const auto size = st::reactionInlineSize;
const auto skip = (size - st::reactionInlineImage) / 2; const auto skip = (size - st::reactionInlineImage) / 2;
const auto inbubble = (_data.flags & InlineListData::Flag::InBubble); const auto inbubble = (_data.flags & InlineListData::Flag::InBubble);
const auto animated = (_animation && context.reactionEffects) const auto animated = (_animation && context.reactionInfo)
? _animation->playingAroundEmoji() ? _animation->playingAroundEmoji()
: QString(); : QString();
const auto flipped = (_data.flags & Data::Flag::Flipped); const auto flipped = (_data.flags & Data::Flag::Flipped);
if (_animation && context.reactionEffects && animated.isEmpty()) { if (_animation && context.reactionInfo && animated.isEmpty()) {
_animation = nullptr; _animation = nullptr;
} }
p.setFont(st::semiboldFont); p.setFont(st::semiboldFont);
@ -342,7 +342,7 @@ void InlineList::paint(
p.drawImage(image.topLeft(), button.image); p.drawImage(image.topLeft(), button.image);
} }
if (animating) { if (animating) {
context.reactionEffects->paint = [=](QPainter &p) { context.reactionInfo->effectPaint = [=](QPainter &p) {
return _animation->paintGetArea(p, QPoint(), image); return _animation->paintGetArea(p, QPoint(), image);
}; };
} }
@ -480,7 +480,12 @@ InlineListData InlineListDataFromMessage(not_null<Message*> message) {
return true; return true;
}(); }();
if (showUserpics) { if (showUserpics) {
result.recent = recent; result.recent.reserve(recent.size());
for (const auto &[emoji, list] : recent) {
result.recent.emplace(emoji).first->second = list
| ranges::view::transform(&Data::RecentReaction::peer)
| ranges::to_vector;
}
} }
result.chosenReaction = item->chosenReaction(); result.chosenReaction = item->chosenReaction();
if (!result.chosenReaction.isEmpty()) { if (!result.chosenReaction.isEmpty()) {

View file

@ -118,12 +118,12 @@ dialogsToUp: TwoIconButton(historyToDown) {
} }
historyUnreadMentions: TwoIconButton(historyToDown) { historyUnreadMentions: TwoIconButton(historyToDown) {
iconAbove: icon {{ "history_unread_mention", historyToDownFg, point(16px, 16px) }}; iconAbove: icon {{ "history_unread_mention", historyToDownFg }};
iconAboveOver: icon {{ "history_unread_mention", historyToDownFgOver, point(16px, 16px) }}; iconAboveOver: icon {{ "history_unread_mention", historyToDownFgOver }};
} }
historyUnreadReactions: TwoIconButton(historyToDown) { historyUnreadReactions: TwoIconButton(historyToDown) {
iconAbove: icon {{ "history_unread_reaction", historyToDownFg, point(16px, 16px) }}; iconAbove: icon {{ "history_unread_reaction", historyToDownFg }};
iconAboveOver: icon {{ "history_unread_reaction", historyToDownFgOver, point(16px, 16px) }}; iconAboveOver: icon {{ "history_unread_reaction", historyToDownFgOver }};
} }
historyUnreadThingsSkip: 4px; historyUnreadThingsSkip: 4px;

View file

@ -90,15 +90,16 @@ struct MessageImageStyle {
style::icon historyVideoMessageMute = { Qt::Uninitialized }; style::icon historyVideoMessageMute = { Qt::Uninitialized };
}; };
struct ReactionEffectPainter { struct ReactionPaintInfo {
QPoint offset; QPoint position;
Fn<QRect(QPainter&)> paint; QPoint effectOffset;
Fn<QRect(QPainter&)> effectPaint;
}; };
struct ChatPaintContext { struct ChatPaintContext {
not_null<const ChatStyle*> st; not_null<const ChatStyle*> st;
const BubblePattern *bubblesPattern = nullptr; const BubblePattern *bubblesPattern = nullptr;
ReactionEffectPainter *reactionEffects = nullptr; ReactionPaintInfo *reactionInfo = nullptr;
QRect viewport; QRect viewport;
QRect clip; QRect clip;
TextSelection selection; TextSelection selection;