diff --git a/Telegram/Resources/icons/history_unread_mention.png b/Telegram/Resources/icons/history_unread_mention.png index 7f717b1c3..5e06356cd 100644 Binary files a/Telegram/Resources/icons/history_unread_mention.png and b/Telegram/Resources/icons/history_unread_mention.png differ diff --git a/Telegram/Resources/icons/history_unread_mention@2x.png b/Telegram/Resources/icons/history_unread_mention@2x.png index 02555b8e4..fa7f3f655 100644 Binary files a/Telegram/Resources/icons/history_unread_mention@2x.png and b/Telegram/Resources/icons/history_unread_mention@2x.png differ diff --git a/Telegram/Resources/icons/history_unread_mention@3x.png b/Telegram/Resources/icons/history_unread_mention@3x.png index 198089ac0..67c6286f9 100644 Binary files a/Telegram/Resources/icons/history_unread_mention@3x.png and b/Telegram/Resources/icons/history_unread_mention@3x.png differ diff --git a/Telegram/Resources/icons/history_unread_reaction.png b/Telegram/Resources/icons/history_unread_reaction.png index 3a6386dd2..fd4cb4d26 100644 Binary files a/Telegram/Resources/icons/history_unread_reaction.png and b/Telegram/Resources/icons/history_unread_reaction.png differ diff --git a/Telegram/Resources/icons/history_unread_reaction@2x.png b/Telegram/Resources/icons/history_unread_reaction@2x.png index b4581adb3..b5ca42b12 100644 Binary files a/Telegram/Resources/icons/history_unread_reaction@2x.png and b/Telegram/Resources/icons/history_unread_reaction@2x.png differ diff --git a/Telegram/Resources/icons/history_unread_reaction@3x.png b/Telegram/Resources/icons/history_unread_reaction@3x.png index c1f26c076..92763dd28 100644 Binary files a/Telegram/Resources/icons/history_unread_reaction@3x.png and b/Telegram/Resources/icons/history_unread_reaction@3x.png differ diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index e00a9a85f..f5658abb1 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -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>>(); + auto parsed = base::flat_map>(); 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 &MessageReactions::list() const { } auto MessageReactions::recent() const --> const base::flat_map>> & { +-> const base::flat_map> & { return _recent; } diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index a83fe1018..50935f531 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -125,6 +125,20 @@ private: }; +struct RecentReaction { + not_null 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 item); @@ -137,7 +151,7 @@ public: bool ignoreChosen); [[nodiscard]] const base::flat_map &list() const; [[nodiscard]] auto recent() const - -> const base::flat_map>> &; + -> const base::flat_map> &; [[nodiscard]] QString chosen() const; [[nodiscard]] bool empty() const; @@ -146,7 +160,7 @@ private: QString _chosen; base::flat_map _list; - base::flat_map>> _recent; + base::flat_map> _recent; }; diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 087c04fee..e02caaaf2 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -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 }}; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 8fa508f5d..0c4837bf3 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -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, diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index e3b4af378..1a44719e8 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -693,7 +693,7 @@ not_null History::addNewLocalMessage( } void History::setUnreadThingsKnown() { - _flags &= ~Flag::UnreadThingsKnown; + _flags |= Flag::UnreadThingsKnown; } HistoryUnreadThings::Proxy History::unreadMentions() { diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 9eae92ee3..7dc5495f1 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -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>(); + 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>(); + 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 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 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 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()) { - 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 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 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()) { + 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) { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 40a694c46..ef535048b 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -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 &HistoryItem::reactions() const { } auto HistoryItem::recentReactions() const --> const base::flat_map>> & { +-> const base::flat_map> & { static const auto kEmpty = base::flat_map< QString, - std::vector>>(); + std::vector>(); return _reactions ? _reactions->recent() : kEmpty; } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 83f13960c..8f1a285a4 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -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 &reactions() const; [[nodiscard]] auto recentReactions() const - -> const base::flat_map>> &; + -> const base::flat_map< + QString, + std::vector> &; [[nodiscard]] bool canViewReactions() const; [[nodiscard]] QString chosenReaction() const; [[nodiscard]] QString lookupHisReaction() const; diff --git a/Telegram/SourceFiles/history/history_unread_things.h b/Telegram/SourceFiles/history/history_unread_things.h index 3c0d0ac05..387cb58a4 100644 --- a/Telegram/SourceFiles/history/history_unread_things.h +++ b/Telegram/SourceFiles/history/history_unread_things.h @@ -108,7 +108,8 @@ public: known) , _history(history) , _data(data) - , _type(type) { + , _type(type) + , _known(known) { } void setCount(int count); diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index a6603f317..72a8d8661 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -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); }; } diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index b397d68eb..2079d2652 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -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); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 547e30282..7a28cd56d 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -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()); } diff --git a/Telegram/SourceFiles/history/view/history_view_react_button.cpp b/Telegram/SourceFiles/history/view/history_view_react_button.cpp index dc4a47f91..4453ae48e 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_react_button.cpp @@ -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 Manager::lookupEffectArea(FullMsgId itemId) const { void Manager::startEffectsCollection() { _collectedEffects.clear(); - _currentEffect = {}; + _currentReactionInfo = {}; } -not_null Manager::currentReactionEffect() { - return &_currentEffect; +auto Manager::currentReactionPaintInfo() +-> not_null { + 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); } diff --git a/Telegram/SourceFiles/history/view/history_view_react_button.h b/Telegram/SourceFiles/history/view/history_view_react_button.h index cc9c056fd..f1a7fc05f 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_button.h +++ b/Telegram/SourceFiles/history/view/history_view_react_button.h @@ -173,8 +173,8 @@ public: [[nodiscard]] std::optional lookupEffectArea( FullMsgId itemId) const; void startEffectsCollection(); - [[nodiscard]] auto currentReactionEffect() - -> not_null; + [[nodiscard]] auto currentReactionPaintInfo() + -> not_null; void recordCurrentReactionEffect(FullMsgId itemId, QPoint origin); bool showContextMenu( @@ -354,8 +354,8 @@ private: base::flat_map _activeEffectAreas; - Ui::ReactionEffectPainter _currentEffect; - base::flat_map _collectedEffects; + Ui::ReactionPaintInfo _currentReactionInfo; + base::flat_map _collectedEffects; base::unique_qptr _menu; rpl::event_stream _faveRequests; diff --git a/Telegram/SourceFiles/history/view/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/history_view_reactions.cpp index 0ad430e9f..f495bf3bc 100644 --- a/Telegram/SourceFiles/history/view/history_view_reactions.cpp +++ b/Telegram/SourceFiles/history/view/history_view_reactions.cpp @@ -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) { 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()) { diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index b3f71d6de..b16f1849f 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -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; diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 07755c250..72d2f891e 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -90,15 +90,16 @@ struct MessageImageStyle { style::icon historyVideoMessageMute = { Qt::Uninitialized }; }; -struct ReactionEffectPainter { - QPoint offset; - Fn paint; +struct ReactionPaintInfo { + QPoint position; + QPoint effectOffset; + Fn effectPaint; }; struct ChatPaintContext { not_null st; const BubblePattern *bubblesPattern = nullptr; - ReactionEffectPainter *reactionEffects = nullptr; + ReactionPaintInfo *reactionInfo = nullptr; QRect viewport; QRect clip; TextSelection selection;