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);
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) {
_recent.erase(j);
}
@ -502,7 +504,7 @@ void MessageReactions::add(const QString &reaction) {
if (!reaction.isEmpty()) {
if (_item->canViewReactions()) {
auto &list = _recent[reaction];
list.insert(begin(list), self);
list.insert(begin(list), RecentReaction{ self });
}
++_list[reaction];
}
@ -557,15 +559,16 @@ void MessageReactions::set(
_chosen = QString();
}
}
auto parsed = base::flat_map<
QString,
std::vector<not_null<PeerData*>>>();
auto parsed = base::flat_map<QString, std::vector<RecentReaction>>();
for (const auto &reaction : recent) {
reaction.match([&](const MTPDmessagePeerReaction &data) {
const auto emoji = qs(data.vreaction());
if (_list.contains(emoji)) {
parsed[emoji].push_back(
owner.peer(peerFromMTP(data.vpeer_id())));
parsed[emoji].push_back(RecentReaction{
.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
-> const base::flat_map<QString, std::vector<not_null<PeerData*>>> & {
-> const base::flat_map<QString, std::vector<RecentReaction>> & {
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 {
public:
explicit MessageReactions(not_null<HistoryItem*> item);
@ -137,7 +151,7 @@ public:
bool ignoreChosen);
[[nodiscard]] const base::flat_map<QString, int> &list() 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]] bool empty() const;
@ -146,7 +160,7 @@ private:
QString _chosen;
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 }};
dialogsUnreadMentionOver: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgOver }};
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 displayUnreadMark,
bool displayMentionBadge,
bool displayReactionBadge,
int unreadCount,
bool selected,
bool active,
@ -95,7 +96,10 @@ void PaintNarrowCounter(
const auto counter = (unreadCount > 0)
? QString::number(unreadCount)
: QString();
const auto allowDigits = displayMentionBadge ? 1 : 3;
const auto allowDigits = (displayMentionBadge
|| displayReactionBadge)
? 1
: 3;
const auto unreadRight = st::dialogsPadding.x()
+ st::dialogsPhotoSize;
const auto unreadTop = st::dialogsPadding.y()
@ -115,7 +119,7 @@ void PaintNarrowCounter(
allowDigits);
skipBeforeMention += badge.width() + st.padding;
}
if (displayMentionBadge) {
if (displayMentionBadge || displayReactionBadge) {
const auto counter = QString();
const auto unreadRight = st::dialogsPadding.x()
+ st::dialogsPhotoSize
@ -136,11 +140,17 @@ void PaintNarrowCounter(
unreadRight,
unreadTop,
st);
(st.active
? st::dialogsUnreadMentionActive
: st.selected
? st::dialogsUnreadMentionOver
: st::dialogsUnreadMention).paintInCenter(p, badge);
(displayMentionBadge
? (st.active
? st::dialogsUnreadMentionActive
: st.selected
? 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 displayUnreadMark,
bool displayMentionBadge,
bool displayReactionBadge,
bool displayPinnedIcon,
int unreadCount,
bool active,
@ -199,7 +210,7 @@ int PaintWideCounter(
hadOneBadge = true;
}
if (displayMentionBadge) {
if (displayMentionBadge || displayReactionBadge) {
const auto counter = QString();
const auto unreadRight = fullWidth
- st::dialogsPadding.x()
@ -221,11 +232,17 @@ int PaintWideCounter(
unreadRight,
unreadTop,
st);
(st.active
? st::dialogsUnreadMentionActive
: st.selected
? st::dialogsUnreadMentionOver
: st::dialogsUnreadMention).paintInCenter(p, badge);
(displayMentionBadge
? (st.active
? st::dialogsUnreadMentionActive
: st.selected
? st::dialogsUnreadMentionOver
: st::dialogsUnreadMention)
: (st.active
? st::dialogsUnreadReactionActive
: st.selected
? st::dialogsUnreadReactionOver
: st::dialogsUnreadReaction)).paintInCenter(p, badge);
availableWidth -= badge.width()
+ st.padding
+ (hadOneBadge ? st::dialogsUnreadPadding : 0);
@ -779,8 +796,10 @@ void RowPainter::paint(
: QDateTime();
}();
const auto displayMentionBadge = history
? history->unreadMentions().has()
: false;
&& history->unreadMentions().has();
const auto displayReactionBadge = !displayMentionBadge
&& history
&& history->unreadReactions().has();
const auto displayUnreadCounter = [&] {
if (displayMentionBadge
&& unreadCount == 1
@ -796,6 +815,7 @@ void RowPainter::paint(
&& unreadMark;
const auto displayPinnedIcon = !displayUnreadCounter
&& !displayMentionBadge
&& !displayReactionBadge
&& !displayUnreadMark
&& entry->isPinnedDialog(filterId)
&& (filterId || !entry->fixedOnTopIndex());
@ -824,6 +844,7 @@ void RowPainter::paint(
displayUnreadCounter,
displayUnreadMark,
displayMentionBadge,
displayReactionBadge,
displayPinnedIcon,
unreadCount,
active,
@ -868,6 +889,7 @@ void RowPainter::paint(
displayUnreadCounter,
displayUnreadMark,
displayMentionBadge,
displayReactionBadge,
unreadCount,
selected,
active,
@ -943,6 +965,9 @@ void RowPainter::paint(
const auto mentionMuted = (history->folder() != nullptr);
const auto displayMentionBadge = displayUnreadInfo
&& history->unreadMentions().has();
const auto displayReactionBadge = displayUnreadInfo
&& !displayMentionBadge
&& history->unreadReactions().has();
const auto displayUnreadCounter = (unreadCount > 0);
const auto displayUnreadMark = !displayUnreadCounter
&& !displayMentionBadge
@ -961,6 +986,7 @@ void RowPainter::paint(
displayUnreadCounter,
displayUnreadMark,
displayMentionBadge,
displayReactionBadge,
displayPinnedIcon,
unreadCount,
active,
@ -987,6 +1013,7 @@ void RowPainter::paint(
displayUnreadCounter,
displayUnreadMark,
displayMentionBadge,
displayReactionBadge,
unreadCount,
selected,
active,

View file

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

View file

@ -848,10 +848,6 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
mouseActionUpdate();
}
const auto guard = gsl::finally([&] {
_userpicsCache.clear();
});
Painter p(this);
auto clip = e->rect();
@ -872,7 +868,6 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
const auto now = crl::now();
const auto historyDisplayedEmpty = _history->isDisplayedEmpty()
&& (!_migrated || _migrated->isDisplayedEmpty());
bool noHistoryDisplayed = historyDisplayedEmpty;
if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) {
const auto st = context.st;
const auto stm = &st->messageStyle(false, false);
@ -899,254 +894,251 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
_emptyPainter = nullptr;
}
_reactionsManager->startEffectsCollection();
if (!noHistoryDisplayed) {
auto readMentions = base::flat_set<not_null<HistoryItem*>>();
const auto mtop = migratedTop();
const auto htop = historyTop();
if (historyDisplayedEmpty || (mtop < 0 && htop < 0)) {
return;
}
adjustCurrent(clip.top());
auto drawToY = clip.y() + clip.height();
auto selfromy = itemTop(_dragSelFrom);
auto seltoy = itemTop(_dragSelTo);
if (selfromy < 0 || seltoy < 0) {
selfromy = seltoy = -1;
} else {
seltoy += _dragSelTo->height();
auto readTill = (HistoryItem*)nullptr;
auto readContents = base::flat_set<not_null<HistoryItem*>>();
const auto guard = gsl::finally([&] {
if (readTill && _widget->doWeReadServerHistory()) {
session().data().histories().readInboxTill(readTill);
}
if (!readContents.empty() && _widget->doWeReadMentions()) {
session().api().markContentsRead(readContents);
}
_userpicsCache.clear();
});
auto mtop = migratedTop();
auto htop = historyTop();
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 item = view->data();
const auto processPainted = [&](
not_null<Element*> view,
int top,
int height) {
const auto item = view->data();
const auto isSponsored = item->isSponsored();
const auto isUnread = !item->out()
&& item->unread()
&& (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();
context.translate(0, -top);
p.translate(0, top);
if (context.clip.y() < view->height()) while (top < drawToY) {
context.reactionEffects
= _reactionsManager->currentReactionEffect();
adjustCurrent(clip.top());
const auto drawToY = clip.y() + clip.height();
auto selfromy = itemTop(_dragSelFrom);
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.selection = itemRenderSelection(
view,
selfromy - mtop,
seltoy - mtop);
selfromy - htop,
seltoy - htop);
view->draw(p, context);
_reactionsManager->recordCurrentReactionEffect(
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();
processPainted(view, top, height);
}
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 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));
top += height;
context.translate(0, -height);
p.translate(0, height);
const auto middle = top + height / 2;
const auto bottom = top + height;
if (_visibleAreaBottom >= bottom) {
if (!item->out() && item->unread()) {
readTill = item;
}
}
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);
++iItem;
if (iItem == block->messages.size()) {
iItem = 0;
++iBlock;
if (iBlock == _history->blocks.size()) {
break;
}
top += height;
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);
block = _history->blocks[iBlock].get();
}
view = block->messages[iItem].get();
}
if (!readMentions.empty() && _widget->doWeReadMentions()) {
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);
}
context.translate(0, top);
p.translate(0, -top);
}
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) {

View file

@ -337,6 +337,12 @@ bool HistoryItem::isUnreadMention() 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;
}
@ -901,10 +907,10 @@ const base::flat_map<QString, int> &HistoryItem::reactions() 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<
QString,
std::vector<not_null<PeerData*>>>();
std::vector<Data::RecentReaction>>();
return _reactions ? _reactions->recent() : kEmpty;
}

View file

@ -41,6 +41,7 @@ struct RippleAnimation;
namespace Data {
struct MessagePosition;
struct RecentReaction;
class Media;
class MessageReactions;
} // namespace Data
@ -367,7 +368,9 @@ public:
void updateReactionsUnknown();
[[nodiscard]] const base::flat_map<QString, int> &reactions() 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]] QString chosenReaction() const;
[[nodiscard]] QString lookupHisReaction() const;

View file

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

View file

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

View file

@ -1746,8 +1746,8 @@ void ListWidget::paintEvent(QPaintEvent *e) {
p.translate(0, top);
for (auto i = from; i != to; ++i) {
const auto view = *i;
context.reactionEffects
= _reactionsManager->currentReactionEffect();
context.reactionInfo
= _reactionsManager->currentReactionPaintInfo();
context.outbg = view->hasOutLayout();
context.selection = itemRenderSelection(view);
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);
p.translate(reactionsPosition);
_reactions->paint(p, context, g.width(), context.clip.translated(-reactionsPosition));
if (context.reactionEffects && context.reactionEffects->paint) {
context.reactionEffects->offset += reactionsPosition;
if (context.reactionInfo) {
context.reactionInfo->position = 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);
p.translate(reactionsPosition);
_reactions->paint(p, context, g.width(), context.clip.translated(-reactionsPosition));
if (context.reactionEffects && context.reactionEffects->paint) {
context.reactionEffects->offset += reactionsPosition;
if (context.reactionInfo) {
context.reactionInfo->position = reactionsPosition;
}
p.translate(-reactionsPosition);
}
@ -842,10 +842,12 @@ void Message::draw(Painter &p, const PaintContext &context) const {
media->draw(p, context.translated(
-mediaPosition
).withSelection(skipTextSelection(context.selection)));
if (context.reactionEffects
&& context.reactionEffects->paint
&& !_reactions) {
context.reactionEffects->offset += mediaPosition;
if (context.reactionInfo && !displayInfo && !_reactions) {
const auto add = QPoint(0, mediaHeight);
context.reactionInfo->position = mediaPosition + add;
if (context.reactionInfo->effectPaint) {
context.reactionInfo->effectOffset -= add;
}
}
p.translate(-mediaPosition);
}
@ -876,6 +878,13 @@ void Message::draw(Painter &p, const PaintContext &context) const {
inner.top() + inner.height(),
2 * inner.left() + inner.width(),
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) {
const auto o = p.opacity();
p.setOpacity(0.3);
@ -901,8 +910,12 @@ void Message::draw(Painter &p, const PaintContext &context) const {
media->draw(p, context.translated(
-g.topLeft()
).withSelection(skipTextSelection(context.selection)));
if (context.reactionEffects && context.reactionEffects->paint && !_reactions) {
context.reactionEffects->offset += g.topLeft();
if (context.reactionInfo && !_reactions) {
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());
}

View file

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

View file

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

View file

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

View file

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

View file

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