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);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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 }};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|