Display unread reactions badge in chats list.
Before Width: | Height: | Size: 708 B After Width: | Height: | Size: 727 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 977 B After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3 KiB |
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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 }};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -693,7 +693,7 @@ not_null<HistoryItem*> History::addNewLocalMessage(
|
|||
}
|
||||
|
||||
void History::setUnreadThingsKnown() {
|
||||
_flags &= ~Flag::UnreadThingsKnown;
|
||||
_flags |= Flag::UnreadThingsKnown;
|
||||
}
|
||||
|
||||
HistoryUnreadThings::Proxy History::unreadMentions() {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -108,7 +108,8 @@ public:
|
|||
known)
|
||||
, _history(history)
|
||||
, _data(data)
|
||||
, _type(type) {
|
||||
, _type(type)
|
||||
, _known(known) {
|
||||
}
|
||||
|
||||
void setCount(int count);
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|