From ebd4651ac243ce353deae4293e02b0af9ac0d2a6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 21 Jan 2018 22:21:08 +0300 Subject: [PATCH] Manage unread bar using HistoryView::Element-s. --- Telegram/SourceFiles/app.cpp | 3 + Telegram/SourceFiles/base/runtime_composer.h | 38 +-- .../admin_log/history_admin_log_inner.cpp | 6 +- .../admin_log/history_admin_log_inner.h | 1 + Telegram/SourceFiles/history/history.cpp | 242 +++++++++++------- Telegram/SourceFiles/history/history.h | 134 +++++----- .../history/history_inner_widget.cpp | 41 ++- .../history/history_inner_widget.h | 2 + Telegram/SourceFiles/history/history_item.cpp | 41 --- Telegram/SourceFiles/history/history_item.h | 11 - .../history/history_item_components.cpp | 30 --- .../history/history_item_components.h | 23 -- .../history/history_media_types.cpp | 10 +- .../SourceFiles/history/history_widget.cpp | 173 +++++++------ Telegram/SourceFiles/history/history_widget.h | 6 +- .../history/view/history_view_element.cpp | 98 ++++++- .../history/view/history_view_element.h | 35 ++- .../history/view/history_view_list_widget.cpp | 78 ++++-- .../history/view/history_view_list_widget.h | 3 + .../history/view/history_view_message.cpp | 8 +- .../view/history_view_service_message.cpp | 20 +- Telegram/SourceFiles/mainwidget.cpp | 2 +- Telegram/SourceFiles/mainwidget.h | 2 +- 23 files changed, 590 insertions(+), 417 deletions(-) diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index f7791622a..03ffd91e8 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -1225,6 +1225,9 @@ namespace { } History *historyLoaded(const PeerId &peer) { + if (!peer) { + return nullptr; + } return ::histories.find(peer); } diff --git a/Telegram/SourceFiles/base/runtime_composer.h b/Telegram/SourceFiles/base/runtime_composer.h index e04c8e2a4..461f3888e 100644 --- a/Telegram/SourceFiles/base/runtime_composer.h +++ b/Telegram/SourceFiles/base/runtime_composer.h @@ -190,27 +190,33 @@ public: } protected: - void UpdateComponents(uint64 mask = 0) { - if (!_meta()->equals(mask)) { - RuntimeComposerBase tmp(mask); - tmp.swap(*this); - if (_data != zerodata() && tmp._data != zerodata()) { - auto meta = _meta(), wasmeta = tmp._meta(); - for (int i = 0; i < meta->last; ++i) { - auto offset = meta->offsets[i]; - auto wasoffset = wasmeta->offsets[i]; - if (offset >= sizeof(_meta()) && wasoffset >= sizeof(_meta())) { - RuntimeComponentWraps[i].Move(_dataptrunsafe(offset), tmp._dataptrunsafe(wasoffset)); - } + bool UpdateComponents(uint64 mask = 0) { + if (_meta()->equals(mask)) { + return false; + } + RuntimeComposerBase result(mask); + result.swap(*this); + if (_data != zerodata() && result._data != zerodata()) { + const auto meta = _meta(); + const auto wasmeta = result._meta(); + for (auto i = 0; i != meta->last; ++i) { + const auto offset = meta->offsets[i]; + const auto wasoffset = wasmeta->offsets[i]; + if (offset >= sizeof(_meta()) + && wasoffset >= sizeof(_meta())) { + RuntimeComponentWraps[i].Move( + _dataptrunsafe(offset), + result._dataptrunsafe(wasoffset)); } } } + return true; } - void AddComponents(uint64 mask = 0) { - UpdateComponents(_meta()->maskadd(mask)); + bool AddComponents(uint64 mask = 0) { + return UpdateComponents(_meta()->maskadd(mask)); } - void RemoveComponents(uint64 mask = 0) { - UpdateComponents(_meta()->maskremove(mask)); + bool RemoveComponents(uint64 mask = 0) { + return UpdateComponents(_meta()->maskremove(mask)); } private: diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index 43167690b..9ee6a6ab5 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -222,7 +222,7 @@ InnerWidget::InnerWidget( Auth().data().viewResizeRequest( ) | rpl::start_with_next([this](auto view) { if (view->delegate() == this) { - updateSize(); + resizeItem(view); } }, lifetime()); Auth().data().itemViewRefreshRequest( @@ -1618,6 +1618,10 @@ void InnerWidget::repaintItem(const Element *view) { update(0, itemTop(view), width(), view->height()); } +void InnerWidget::resizeItem(not_null view) { + updateSize(); +} + void InnerWidget::refreshItem(not_null view) { // No need to refresh views in admin log. } diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h index dc48a6faf..159d7263b 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -123,6 +123,7 @@ private: int itemTop(not_null view) const; void repaintItem(const Element *view); void refreshItem(not_null view); + void resizeItem(not_null view); QPoint mapPointToItem(QPoint point, const Element *view) const; void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index d1c5d4969..ce7f0a7aa 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -79,13 +79,8 @@ void History::clearLastKeyboard() { lastKeyboardFrom = 0; } -bool History::canHaveFromPhotos() const { - if (peer->isUser() && !peer->isSelf() && !Adaptive::ChatWide()) { - return false; - } else if (isChannel() && !peer->isMegagroup()) { - return false; - } - return true; +int History::height() const { + return _height; } void History::setHasPendingResizedItems() { @@ -259,9 +254,22 @@ bool History::mySendActionUpdated(SendAction::Type type, bool doing) { return true; } -bool History::paintSendAction(Painter &p, int x, int y, int availableWidth, int outerWidth, style::color color, TimeMs ms) { +bool History::paintSendAction( + Painter &p, + int x, + int y, + int availableWidth, + int outerWidth, + style::color color, + TimeMs ms) { if (_sendActionAnimation) { - _sendActionAnimation.paint(p, color, x, y + st::normalFont->ascent, outerWidth, ms); + _sendActionAnimation.paint( + p, + color, + x, + y + st::normalFont->ascent, + outerWidth, + ms); auto animationWidth = _sendActionAnimation.width(); x += animationWidth; availableWidth -= animationWidth; @@ -572,7 +580,11 @@ not_null Histories::findOrInsert(const PeerId &peerId) { return i.value(); } -not_null Histories::findOrInsert(const PeerId &peerId, int32 unreadCount, int32 maxInboxRead, int32 maxOutboxRead) { +not_null Histories::findOrInsert( + const PeerId &peerId, + int unreadCount, + MsgId maxInboxRead, + MsgId maxOutboxRead) { auto i = map.constFind(peerId); if (i == map.cend()) { auto history = peerIsChannel(peerId) @@ -1397,6 +1409,23 @@ void History::clearSendAction(not_null from) { } } +void History::mainViewRemoved( + not_null block, + not_null view) { + if (lastSentMsg == view->data()) { + lastSentMsg = nullptr; + } + if (_firstUnreadView == view) { + getNextFirstUnreadMessage(); + } + if (_unreadBarView == view) { + _unreadBarView = nullptr; + } + if (scrollTopItem == view) { + getNextScrollTopItem(block, view->indexInBlock()); + } +} + void History::newItemAdded(not_null item) { App::checkImageCacheSize(); item->indexAsNewItem(); @@ -1408,9 +1437,7 @@ void History::newItemAdded(not_null item) { from->madeAction(itemServerTime.v); } if (item->out()) { - if (unreadBar) { - unreadBar->destroyUnreadBar(); - } + destroyUnreadBar(); if (!item->unread()) { outboxRead(item); } @@ -1739,18 +1766,23 @@ int History::countUnread(MsgId upTo) { return result; } -void History::updateShowFrom() { - if (showFrom) return; +void History::calculateFirstUnreadMessage() { + if (_firstUnreadView) { + return; + } for (auto i = blocks.cend(); i != blocks.cbegin();) { --i; const auto &messages = (*i)->messages; for (auto j = messages.cend(); j != messages.cbegin();) { --j; - const auto item = (*j)->data(); - if (item->id > 0 && (!item->out() || !showFrom)) { + const auto view = j->get(); + const auto item = view->data(); + if (!IsServerMsgId(item->id)) { + continue; + } else if (!item->out() || !_firstUnreadView) { if (item->id >= inboxReadBefore) { - showFrom = item; + _firstUnreadView = view; } else { return; } @@ -1776,7 +1808,7 @@ MsgId History::inboxRead(MsgId upTo) { } } - showFrom = nullptr; + _firstUnreadView = nullptr; Auth().notifications().clearFromHistory(this); return upTo; @@ -1805,13 +1837,19 @@ HistoryItem *History::lastAvailableMessage() const { void History::setUnreadCount(int newUnreadCount) { if (_unreadCount != newUnreadCount) { if (newUnreadCount == 1) { - if (loadedAtBottom()) showFrom = lastAvailableMessage(); + if (loadedAtBottom()) { + _firstUnreadView = !isEmpty() + ? blocks.back()->messages.back().get() + : nullptr; + } inboxReadBefore = qMax(inboxReadBefore, msgIdForRead()); } else if (!newUnreadCount) { - showFrom = nullptr; + _firstUnreadView = nullptr; inboxReadBefore = qMax(inboxReadBefore, msgIdForRead() + 1); } else { - if (!showFrom && !unreadBar && loadedAtBottom()) updateShowFrom(); + if (!_firstUnreadView && !_unreadBarView && loadedAtBottom()) { + calculateFirstUnreadMessage(); + } } if (inChatList(Dialogs::Mode::All)) { App::histories().unreadIncrement(newUnreadCount - _unreadCount, mute()); @@ -1820,22 +1858,17 @@ void History::setUnreadCount(int newUnreadCount) { } } _unreadCount = newUnreadCount; - if (auto main = App::main()) { - main->unreadCountChanged(this); - } - if (unreadBar) { - int32 count = _unreadCount; - if (peer->migrateTo()) { - if (History *h = App::historyLoaded(peer->migrateTo()->id)) { - count += h->unreadCount(); - } - } + if (_unreadBarView) { + const auto count = chatListUnreadCount(); if (count > 0) { - unreadBar->setUnreadBarCount(count); + _unreadBarView->setUnreadBarCount(count); } else { - unreadBar->setUnreadBarFreezed(); + _unreadBarView->setUnreadBarFreezed(); } } + if (const auto main = App::main()) { + main->unreadCountChanged(this); + } } } @@ -1858,18 +1891,21 @@ bool History::changeMute(bool newMute) { return true; } -void History::getNextShowFrom(HistoryBlock *block, int i) { - const auto setFromMessage = [&](const auto &message) { - const auto item = message->data(); - if (item->id > 0) { - showFrom = item; +void History::getNextFirstUnreadMessage() { + Expects(_firstUnreadView != nullptr); + + const auto block = _firstUnreadView->block(); + const auto index = _firstUnreadView->indexInBlock(); + const auto setFromMessage = [&](const auto &view) { + if (IsServerMsgId(view->data()->id)) { + _firstUnreadView = view.get(); return true; } return false; }; - if (i >= 0) { - auto l = block->messages.size(); - for (++i; i < l; ++i) { + if (index >= 0) { + const auto count = int(block->messages.size()); + for (auto i = index + 1; i != count; ++i) { const auto &message = block->messages[i]; if (setFromMessage(block->messages[i])) { return; @@ -1877,15 +1913,15 @@ void History::getNextShowFrom(HistoryBlock *block, int i) { } } - for (auto j = block->indexInHistory() + 1, s = int(blocks.size()); j < s; ++j) { - block = blocks[j].get(); - for (const auto &message : block->messages) { + const auto count = int(blocks.size()); + for (auto j = block->indexInHistory() + 1; j != count; ++j) { + for (const auto &message : blocks[j]->messages) { if (setFromMessage(message)) { return; } } } - showFrom = nullptr; + _firstUnreadView = nullptr; } QDateTime History::adjustChatListDate() const { @@ -1976,26 +2012,44 @@ void History::getNextScrollTopItem(HistoryBlock *block, int32 i) { } void History::addUnreadBar() { - if (unreadBar || !showFrom || !showFrom->mainView() || !unreadCount()) { + if (_unreadBarView || !_firstUnreadView || !unreadCount()) { return; } - - int32 count = unreadCount(); - if (peer->migrateTo()) { - if (History *h = App::historyLoaded(peer->migrateTo()->id)) { - count += h->unreadCount(); - } + if (const auto count = chatListUnreadCount()) { + _unreadBarView = _firstUnreadView; + _unreadBarView->setUnreadBarCount(count); } - showFrom->setUnreadBarCount(count); - unreadBar = showFrom; } void History::destroyUnreadBar() { - if (unreadBar) { - unreadBar->destroyUnreadBar(); + if (const auto view = base::take(_unreadBarView)) { + view->destroyUnreadBar(); } } +bool History::hasNotFreezedUnreadBar() const { + if (_firstUnreadView) { + if (const auto view = _unreadBarView) { + if (const auto bar = view->Get()) { + return !bar->freezed; + } + } + } + return false; +} + +void History::unsetFirstUnreadMessage() { + _firstUnreadView = nullptr; +} + +HistoryView::Element *History::unreadBar() const { + return _unreadBarView; +} + +HistoryView::Element *History::firstUnreadMessage() const { + return _firstUnreadView; +} + not_null History::addNewInTheMiddle( not_null item, int blockIndex, @@ -2028,10 +2082,16 @@ not_null History::addNewInTheMiddle( int History::chatListUnreadCount() const { const auto result = unreadCount(); - if (const auto from = peer->migrateFrom()) { - if (const auto migrated = App::historyLoaded(from)) { - return result + migrated->unreadCount(); + const auto addFromId = [&] { + if (const auto from = peer->migrateFrom()) { + return from->id; + } else if (const auto to = peer->migrateTo()) { + return to->id; } + return PeerId(0); + }(); + if (const auto migrated = App::historyLoaded(addFromId)) { + return result + migrated->unreadCount(); } return result; } @@ -2108,10 +2168,10 @@ bool History::isReadyFor(MsgId msgId) { return loadedAtBottom(); } if (msgId == ShowAtUnreadMsgId) { - if (peer->migrateFrom()) { // old group history - if (History *h = App::historyLoaded(peer->migrateFrom()->id)) { - if (h->unreadCount()) { - return h->isReadyFor(msgId); + if (const auto migratePeer = peer->migrateFrom()) { + if (const auto migrated = App::historyLoaded(migratePeer)) { + if (migrated->unreadCount()) { + return migrated->isReadyFor(msgId); } } } @@ -2136,12 +2196,14 @@ void History::getReadyFor(MsgId msgId) { } return; } - if (msgId == ShowAtUnreadMsgId && peer->migrateFrom()) { - if (auto h = App::historyLoaded(peer->migrateFrom()->id)) { - if (h->unreadCount()) { - clear(true); - h->getReadyFor(msgId); - return; + if (msgId == ShowAtUnreadMsgId) { + if (const auto migratePeer = peer->migrateFrom()) { + if (const auto migrated = App::historyLoaded(migratePeer)) { + if (migrated->unreadCount()) { + clear(true); + migrated->getReadyFor(msgId); + return; + } } } } @@ -2194,7 +2256,7 @@ bool History::needUpdateInChatList() const { } void History::fixLastMessage(bool wasAtBottom) { - setLastMessage(wasAtBottom ? lastAvailableMessage() : 0); + setLastMessage(wasAtBottom ? lastAvailableMessage() : nullptr); } MsgId History::minMsgId() const { @@ -2227,22 +2289,21 @@ MsgId History::msgIdForRead() const { return result; } -int History::resizeGetHeight(int newWidth) { - const auto resizeAllItems = (width != newWidth); +void History::resizeToWidth(int newWidth) { + const auto resizeAllItems = (_width != newWidth); if (!resizeAllItems && !hasPendingResizedItems()) { - return height; + return; } _flags &= ~(Flag::f_has_pending_resized_items); - width = newWidth; + _width = newWidth; int y = 0; for (const auto &block : blocks) { block->setY(y); y += block->resizeGetHeight(newWidth, resizeAllItems); } - height = y; - return height; + _height = y; } ChannelHistory *History::asChannelHistory() { @@ -2295,11 +2356,11 @@ bool History::removeOrphanMediaGroupPart() { } void History::clear(bool leaveItems) { - if (unreadBar) { - unreadBar = nullptr; + if (_unreadBarView) { + _unreadBarView = nullptr; } - if (showFrom) { - showFrom = nullptr; + if (_firstUnreadView) { + _firstUnreadView = nullptr; } if (lastSentMsg) { lastSentMsg = nullptr; @@ -2475,22 +2536,11 @@ void HistoryBlock::clear(bool leaveItems) { void HistoryBlock::remove(not_null view) { Expects(view->block() == this); - const auto item = view->data(); - auto blockIndex = indexInHistory(); - auto itemIndex = view->indexInBlock(); - if (_history->showFrom == item) { - _history->getNextShowFrom(this, itemIndex); - } - if (_history->lastSentMsg == item) { - _history->lastSentMsg = nullptr; - } - if (_history->unreadBar == item) { - _history->unreadBar = nullptr; - } - if (_history->scrollTopItem && _history->scrollTopItem->data() == item) { - _history->getNextScrollTopItem(this, itemIndex); - } + _history->mainViewRemoved(this, view); + const auto blockIndex = indexInHistory(); + const auto itemIndex = view->indexInBlock(); + const auto item = view->data(); item->clearMainView(); messages.erase(messages.begin() + itemIndex); for (auto i = itemIndex, l = int(messages.size()); i < l; ++i) { diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index c5c01ce85..2d1c80b84 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -47,7 +47,11 @@ public: History *find(const PeerId &peerId); not_null findOrInsert(const PeerId &peerId); - not_null findOrInsert(const PeerId &peerId, int32 unreadCount, int32 maxInboxRead, int32 maxOutboxRead); + not_null findOrInsert( + const PeerId &peerId, + int unreadCount, + MsgId maxInboxRead, + MsgId maxOutboxRead); void clear(); void remove(const PeerId &peer); @@ -153,8 +157,6 @@ public: void applyGroupAdminChanges(const base::flat_map &changes); - virtual ~History(); - HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type); HistoryItem *addToHistory(const MTPMessage &msg); not_null addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags = 0, bool newMsg = true); @@ -176,14 +178,11 @@ public: void newItemAdded(not_null item); int countUnread(MsgId upTo); - void updateShowFrom(); MsgId inboxRead(MsgId upTo); MsgId inboxRead(HistoryItem *wasRead); MsgId outboxRead(MsgId upTo); MsgId outboxRead(HistoryItem *wasRead); - HistoryItem *lastAvailableMessage() const; - int unreadCount() const { return _unreadCount; } @@ -192,9 +191,13 @@ public: return _mute; } bool changeMute(bool newMute); - void getNextShowFrom(HistoryBlock *block, int i); void addUnreadBar(); void destroyUnreadBar(); + bool hasNotFreezedUnreadBar() const; + HistoryView::Element *unreadBar() const; + void calculateFirstUnreadMessage(); + void unsetFirstUnreadMessage(); + HistoryView::Element *firstUnreadMessage() const; void clearNotifications(); bool loadedAtBottom() const; // last message is in the list @@ -210,7 +213,7 @@ public: MsgId maxMsgId() const; MsgId msgIdForRead() const; - int resizeGetHeight(int newWidth); + void resizeToWidth(int newWidth); void removeNotification(HistoryItem *item) { if (!notifies.isEmpty()) { @@ -254,10 +257,6 @@ public: void clearLastKeyboard(); - // optimization for userpics displayed on the left - // if this returns false there is no need to even try to handle them - bool canHaveFromPhotos() const; - int getUnreadMentionsLoadedCount() const { return _unreadMentions.size(); } @@ -278,25 +277,6 @@ public: void eraseFromUnreadMentions(MsgId msgId); void addUnreadMentionsSlice(const MTPmessages_Messages &result); - std::deque> blocks; - - int width = 0; - int height = 0; - int32 msgCount = 0; - MsgId inboxReadBefore = 1; - MsgId outboxReadBefore = 1; - HistoryItem *showFrom = nullptr; - HistoryItem *unreadBar = nullptr; - - not_null peer; - bool oldLoaded = false; - bool newLoaded = true; - HistoryItem *lastMsg = nullptr; - HistoryItem *lastSentMsg = nullptr; - - typedef QList NotifyQueue; - NotifyQueue notifies; - Data::Draft *localDraft() const { return _localDraft.get(); } @@ -340,9 +320,33 @@ public: int y, int size) const override; - // some fields below are a property of a currently displayed instance of this - // conversation history not a property of the conversation history itself -public: + void forgetScrollState() { + scrollTopItem = nullptr; + } + + // find the correct scrollTopItem and scrollTopOffset using given top + // of the displayed window relative to the history start coordinate + void countScrollState(int top); + + virtual ~History(); + + // Still public data. + std::deque> blocks; + + int height() const; + int32 msgCount = 0; + MsgId inboxReadBefore = 1; + MsgId outboxReadBefore = 1; + + not_null peer; + bool oldLoaded = false; + bool newLoaded = true; + HistoryItem *lastMsg = nullptr; + HistoryItem *lastSentMsg = nullptr; + + typedef QList NotifyQueue; + NotifyQueue notifies; + // we save the last showAtMsgId to restore the state when switching // between different conversation histories MsgId showAtMsgId = ShowAtUnreadMsgId; @@ -352,25 +356,7 @@ public: // resulting scrollTop = top(scrollTopItem) + scrollTopOffset HistoryView::Element *scrollTopItem = nullptr; int scrollTopOffset = 0; - void forgetScrollState() { - scrollTopItem = nullptr; - } - // find the correct scrollTopItem and scrollTopOffset using given top - // of the displayed window relative to the history start coord - void countScrollState(int top); - -protected: - // when this item is destroyed scrollTopItem just points to the next one - // and scrollTopOffset remains the same - // if we are at the bottom of the window scrollTopItem == nullptr and - // scrollTopOffset is undefined - void getNextScrollTopItem(HistoryBlock *block, int32 i); - - // helper method for countScrollState(int top) - void countScrollTopItem(int top); - -public: bool lastKeyboardInited = false; bool lastKeyboardUsed = false; MsgId lastKeyboardId = 0; @@ -382,11 +368,18 @@ public: Text cloudDraftTextCache; protected: + // when this item is destroyed scrollTopItem just points to the next one + // and scrollTopOffset remains the same + // if we are at the bottom of the window scrollTopItem == nullptr and + // scrollTopOffset is undefined + void getNextScrollTopItem(HistoryBlock *block, int32 i); + + // helper method for countScrollState(int top) + void countScrollTopItem(int top); + void clearOnDestroy(); HistoryItem *addNewToLastBlock(const MTPMessage &msg, NewMessageType type); - friend class HistoryBlock; - // this method just removes a block from the blocks list // when the last item from this block was detached and // calls the required previousItemChanged() @@ -425,6 +418,20 @@ protected: } private: + friend class HistoryBlock; + + enum class Flag { + f_has_pending_resized_items = (1 << 0), + }; + using Flags = base::flags; + friend inline constexpr auto is_flag_type(Flag) { + return true; + }; + + void mainViewRemoved( + not_null block, + not_null view); + QDateTime adjustChatListDate() const override; void removeDialog() override; void changedInChatListHook(Dialogs::Mode list, bool added) override; @@ -451,15 +458,20 @@ private: void addItemsToLists(const std::vector> &items); void clearSendAction(not_null from); - enum class Flag { - f_has_pending_resized_items = (1 << 0), - }; - using Flags = base::flags; - friend inline constexpr auto is_flag_type(Flag) { return true; }; + HistoryItem *lastAvailableMessage() const; + void getNextFirstUnreadMessage(); + + // Creates if necessary a new block for adding item. + // Depending on isBuildingFrontBlock() gets front or back block. + HistoryBlock *prepareBlockForAddingItem(); Flags _flags = 0; bool _mute = false; int _unreadCount = 0; + int _width = 0; + int _height = 0; + HistoryView::Element *_unreadBarView = nullptr; + HistoryView::Element *_firstUnreadView = nullptr; base::optional _unreadMentionsCount; base::flat_set _unreadMentions; @@ -473,10 +485,6 @@ private: }; std::unique_ptr _buildingFrontBlock; - // Creates if necessary a new block for adding item. - // Depending on isBuildingFrontBlock() gets front or back block. - HistoryBlock *prepareBlockForAddingItem(); - std::unique_ptr _localDraft, _cloudDraft; std::unique_ptr _editDraft; MessageIdsList _forwardDraft; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index fb0745103..5915ba892 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -217,7 +217,7 @@ void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Met if (historytop < 0 || history->isEmpty()) { return; } - if (_visibleAreaBottom <= historytop || historytop + history->height <= _visibleAreaTop) { + if (_visibleAreaBottom <= historytop || historytop + history->height() <= _visibleAreaTop) { return; } @@ -302,9 +302,18 @@ void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Met } } +bool HistoryInner::canHaveFromUserpics() const { + if (_peer->isUser() && !_peer->isSelf() && !Adaptive::ChatWide()) { + return false; + } else if (_peer->isChannel() && !_peer->isMegagroup()) { + return false; + } + return true; +} + template void HistoryInner::enumerateUserpics(Method method) { - if ((!_history || !_history->canHaveFromPhotos()) && (!_migrated || !_migrated->canHaveFromPhotos())) { + if (!canHaveFromUserpics()) { return; } @@ -620,7 +629,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { p, st::historyPhotoLeft, userpicTop, - message->history()->width, + width(), st::msgPhotoSize); } return true; @@ -635,6 +644,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { //// if item top is before this value always show date as a floating date //int showFloatingBefore = height() - 2 * (_visibleAreaBottom - _visibleAreaTop) - dateHeight; + auto scrollDateOpacity = _scrollDateOpacity.current(ms, _scrollDateShown ? 1. : 0.); enumerateDates([&](not_null view, int itemtop, int dateTop) { // stop the enumeration if the date is above the painted rect @@ -661,16 +671,17 @@ void HistoryInner::paintEvent(QPaintEvent *e) { auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity; if (opacity > 0.) { p.setOpacity(opacity); - int dateY = /*noFloatingDate ? itemtop :*/ (dateTop - st::msgServiceMargin.top()); - int width = item->history()->width; + const auto dateY = false // noFloatingDate + ? itemtop + : (dateTop - st::msgServiceMargin.top()); if (const auto date = item->Get()) { - date->paint(p, dateY, width); + date->paint(p, dateY, _contentWidth); } else { HistoryView::ServiceMessagePainter::paintDate( p, item->date, dateY, - width); + _contentWidth); } } } @@ -1825,15 +1836,17 @@ void HistoryInner::keyPressEvent(QKeyEvent *e) { } void HistoryInner::recountHistoryGeometry() { - int visibleHeight = _scroll->height(); + _contentWidth = _scroll->width(); + + const auto visibleHeight = _scroll->height(); int oldHistoryPaddingTop = qMax(visibleHeight - historyHeight() - st::historyPaddingBottom, 0); if (_botAbout && !_botAbout->info->text.isEmpty()) { accumulate_max(oldHistoryPaddingTop, st::msgMargin.top() + st::msgMargin.bottom() + st::msgPadding.top() + st::msgPadding.bottom() + st::msgNameFont->height + st::botDescSkip + _botAbout->height); } - _history->resizeGetHeight(_scroll->width()); + _history->resizeToWidth(_contentWidth); if (_migrated) { - _migrated->resizeGetHeight(_scroll->width()); + _migrated->resizeToWidth(_contentWidth); } // with migrated history we perhaps do not need to display first _history message @@ -2339,7 +2352,7 @@ void HistoryInner::onUpdateSelected() { } dateWidth += st::msgServicePadding.left() + st::msgServicePadding.right(); auto dateLeft = st::msgServiceMargin.left(); - auto maxwidth = item->history()->width; + auto maxwidth = _contentWidth; if (Adaptive::ChatWide()) { maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); } @@ -2549,9 +2562,9 @@ void HistoryInner::updateDragSelection(Element *dragSelFrom, Element *dragSelTo, int HistoryInner::historyHeight() const { int result = 0; if (!_history || _history->isEmpty()) { - result += _migrated ? _migrated->height : 0; + result += _migrated ? _migrated->height() : 0; } else { - result += _history->height - _historySkipHeight + (_migrated ? _migrated->height : 0); + result += _history->height() - _historySkipHeight + (_migrated ? _migrated->height() : 0); } return result; } @@ -2574,7 +2587,7 @@ int HistoryInner::migratedTop() const { int HistoryInner::historyTop() const { int mig = migratedTop(); - return (_history && !_history->isEmpty()) ? (mig >= 0 ? (mig + _migrated->height - _historySkipHeight) : _historyPaddingTop) : -1; + return (_history && !_history->isEmpty()) ? (mig >= 0 ? (mig + _migrated->height() - _historySkipHeight) : _historyPaddingTop) : -1; } int HistoryInner::historyDrawTop() const { diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 759993f10..455d33019 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -181,6 +181,7 @@ private: template void enumerateDates(Method method); + bool canHaveFromUserpics() const; void mouseActionStart(const QPoint &screenPos, Qt::MouseButton button); void mouseActionUpdate(const QPoint &screenPos); void mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button); @@ -276,6 +277,7 @@ private: not_null _peer; not_null _history; History *_migrated = nullptr; + int _contentWidth = 0; int _historyPaddingTop = 0; // with migrated history we perhaps do not need to display first _history message diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index df455912a..92a58cb34 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -577,47 +577,6 @@ bool HistoryItem::unread() const { return (_flags & MTPDmessage_ClientFlag::f_clientside_unread); } -void HistoryItem::destroyUnreadBar() { - if (Has()) { - Assert(!isLogEntry()); - - RemoveComponents(HistoryMessageUnreadBar::Bit()); - Auth().data().requestItemResize(this); - if (_history->unreadBar == this) { - _history->unreadBar = nullptr; - } - // #TODO recount attach to previous - } -} - -void HistoryItem::setUnreadBarCount(int count) { - Expects(!isLogEntry()); - - if (count > 0) { - if (!Has()) { - AddComponents(HistoryMessageUnreadBar::Bit()); - Auth().data().requestItemResize(this); - // #TODO recount attach to previous - } - const auto bar = Get(); - if (bar->_freezed) { - return; - } - bar->init(count); - Auth().data().requestItemRepaint(this); - } else { - destroyUnreadBar(); - } -} - -void HistoryItem::setUnreadBarFreezed() { - Expects(!isLogEntry()); - - if (const auto bar = Get()) { - bar->_freezed = true; - } -} - MessageGroupId HistoryItem::groupId() const { return _groupId; } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index d3538e5c3..19a8e528d 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -244,17 +244,6 @@ public: QString authorOriginal() const; MsgId idOriginal() const; - // count > 0 - creates the unread bar if necessary and - // sets unread messages count if bar is not freezed yet - // count <= 0 - destroys the unread bar - void setUnreadBarCount(int count); - void destroyUnreadBar(); - - // marks the unread bar as freezed so that unread - // messages count will not change for this bar - // when the new messages arrive in this chat history - void setUnreadBarFreezed(); - int displayedDateHeight() const; bool displayDate() const; diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 312a438b5..8adbf4b33 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -798,36 +798,6 @@ void HistoryMessageReplyMarkup::create( } } -void HistoryMessageUnreadBar::init(int count) { - if (_freezed) return; - _text = lng_unread_bar(lt_count, count); - _width = st::semiboldFont->width(_text); -} - -int HistoryMessageUnreadBar::height() { - return st::historyUnreadBarHeight + st::historyUnreadBarMargin; -} - -int HistoryMessageUnreadBar::marginTop() { - return st::lineWidth + st::historyUnreadBarMargin; -} - -void HistoryMessageUnreadBar::paint(Painter &p, int y, int w) const { - p.fillRect(0, y + marginTop(), w, height() - marginTop() - st::lineWidth, st::historyUnreadBarBg); - p.fillRect(0, y + height() - st::lineWidth, w, st::lineWidth, st::historyUnreadBarBorder); - p.setFont(st::historyUnreadBarFont); - p.setPen(st::historyUnreadBarFg); - - int left = st::msgServiceMargin.left(); - int maxwidth = w; - if (Adaptive::ChatWide()) { - maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); - } - w = maxwidth; - - p.drawText((w - _width) / 2, y + marginTop() + (st::historyUnreadBarHeight - 2 * st::lineWidth - st::historyUnreadBarFont->height) / 2 + st::historyUnreadBarFont->ascent, _text); -} - void HistoryMessageDate::init(const QDateTime &date) { _text = langDayOfMonthFull(date.date()); _width = st::msgServiceFont->width(_text); diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 68e9dce4b..ad66b61ff 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -335,29 +335,6 @@ struct HistoryMessageDate : public RuntimeComponent { - void init(int count); - - static int height(); - static int marginTop(); - - void paint(Painter &p, int y, int w) const; - - QString _text; - int _width = 0; - - // If unread bar is freezed the new messages do not - // increment the counter displayed by this bar. - // - // It happens when we've opened the conversation and - // we've seen the bar and new messages are marked as read - // as soon as they are added to the chat history. - bool _freezed = false; - -}; - // Special type of Component for the channel actions log. struct HistoryMessageLogEntryOriginal : public RuntimeComponent { diff --git a/Telegram/SourceFiles/history/history_media_types.cpp b/Telegram/SourceFiles/history/history_media_types.cpp index 8442a8e93..a758667ce 100644 --- a/Telegram/SourceFiles/history/history_media_types.cpp +++ b/Telegram/SourceFiles/history/history_media_types.cpp @@ -2363,9 +2363,8 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM } else if (!inWebPage) { auto fullRight = paintx + usex + usew; auto fullBottom = painty + painth; - auto maxRight = item->history()->width - st::msgMargin.left(); - // #TODO view media - if (item->history()->canHaveFromPhotos()) { + auto maxRight = _parent->width() - st::msgMargin.left(); + if (_parent->hasFromPhoto()) { maxRight -= st::msgMargin.right(); } else { maxRight -= st::msgMargin.left(); @@ -2505,9 +2504,8 @@ HistoryTextState HistoryGif::getState(QPoint point, HistoryStateRequest request) if (isRound || _caption.isEmpty()) { auto fullRight = usex + paintx + usew; auto fullBottom = painty + painth; - // #TODO view media - auto maxRight = _parent->data()->history()->width - st::msgMargin.left(); - if (_parent->data()->history()->canHaveFromPhotos()) { + auto maxRight = _parent->width() - st::msgMargin.left(); + if (_parent->hasFromPhoto()) { maxRight -= st::msgMargin.right(); } else { maxRight -= st::msgMargin.left(); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index a24980249..478cc12ae 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -2204,6 +2204,13 @@ void HistoryWidget::destroyUnreadBar() { void HistoryWidget::newUnreadMsg(History *history, HistoryItem *item) { if (_history == history) { + // If we get here in non-resized state we can't rely on results of + // doWeReadServerHistory() and mark chat as read. + // If we receive N messages being not at bottom: + // - on first message we set unreadcount += 1, firstUnreadMessage. + // - on second we get wrong doWeReadServerHistory() and read both. + Auth().data().sendHistoryChangeNotifications(); + if (_scroll->scrollTop() + 1 > _scroll->scrollTopMax()) { destroyUnreadBar(); } @@ -2229,10 +2236,12 @@ void HistoryWidget::historyToDown(History *history) { } } -void HistoryWidget::unreadCountChanged(History *history) { +void HistoryWidget::unreadCountChanged(not_null history) { if (history == _history || history == _migrated) { updateHistoryDownVisibility(); - _historyDown->setUnreadCount(_history->unreadCount() + (_migrated ? _migrated->unreadCount() : 0)); + _historyDown->setUnreadCount( + _history->unreadCount() + + (_migrated ? _migrated->unreadCount() : 0)); } } @@ -2452,16 +2461,15 @@ bool HistoryWidget::doWeReadServerHistory() const { int scrollTop = _scroll->scrollTop(); if (scrollTop + 1 > _scroll->scrollTopMax()) return true; - auto showFrom = (_migrated && _migrated->showFrom) ? _migrated->showFrom : (_history ? _history->showFrom : nullptr); - if (showFrom && showFrom->mainView()) { - int scrollBottom = scrollTop + _scroll->height(); - if (scrollBottom > _list->itemTop(showFrom)) return true; + if (const auto unread = firstUnreadMessage()) { + const auto scrollBottom = scrollTop + _scroll->height(); + if (scrollBottom > _list->itemTop(unread)) { + return true; + } } } - if (historyHasNotFreezedUnreadBar(_history)) { - return true; - } - if (historyHasNotFreezedUnreadBar(_migrated)) { + if (_history->hasNotFreezedUnreadBar() + || (_migrated && _migrated->hasNotFreezedUnreadBar())) { return true; } return false; @@ -2473,15 +2481,6 @@ bool HistoryWidget::doWeReadMentions() const { return true; } -bool HistoryWidget::historyHasNotFreezedUnreadBar(History *history) const { - if (history && history->showFrom && history->showFrom->mainView() && history->unreadBar) { - if (auto unreadBar = history->unreadBar->Get()) { - return !unreadBar->_freezed; - } - } - return false; -} - void HistoryWidget::firstLoadMessages() { if (!_history || _firstLoadRequest) return; @@ -2707,10 +2706,12 @@ void HistoryWidget::visibleAreaUpdated() { auto scrollBottom = scrollTop + _scroll->height(); _list->visibleAreaUpdated(scrollTop, scrollBottom); if (_history->loadedAtBottom() && (_history->unreadCount() > 0 || (_migrated && _migrated->unreadCount() > 0))) { - auto showFrom = (_migrated && _migrated->showFrom) ? _migrated->showFrom : (_history ? _history->showFrom : nullptr); - auto showFromVisible = (showFrom && showFrom->mainView() && scrollBottom > _list->itemTop(showFrom)); - auto atBottom = (scrollTop >= _scroll->scrollTopMax()); - if ((showFromVisible || atBottom) && App::wnd()->doWeReadServerHistory()) { + const auto unread = firstUnreadMessage(); + const auto unreadVisible = unread + && (scrollBottom > _list->itemTop(unread)); + const auto atBottom = (scrollTop >= _scroll->scrollTopMax()); + if ((unreadVisible || atBottom) + && App::wnd()->doWeReadServerHistory()) { Auth().api().readServerHistory(_history); } } @@ -4694,8 +4695,8 @@ int HistoryWidget::countInitialScrollTop() { result = itemTopForHighlight(view); enqueueMessageHighlight(view); } - } else if (_history->unreadBar || (_migrated && _migrated->unreadBar)) { - result = unreadBarTop(); + } else if (const auto top = unreadBarTop()) { + result = *top; } else { return countAutomaticScrollTop(); } @@ -4704,28 +4705,18 @@ int HistoryWidget::countInitialScrollTop() { int HistoryWidget::countAutomaticScrollTop() { auto result = ScrollMax; - if (_migrated && _migrated->showFrom) { - result = _list->itemTop(_migrated->showFrom); - if (result < _scroll->scrollTopMax() + HistoryMessageUnreadBar::height() - HistoryMessageUnreadBar::marginTop()) { - _migrated->addUnreadBar(); + if (const auto unread = firstUnreadMessage()) { + result = _list->itemTop(unread); + const auto possibleUnreadBarTop = _scroll->scrollTopMax() + + HistoryView::UnreadBar::height() + - HistoryView::UnreadBar::marginTop(); + if (result < possibleUnreadBarTop) { + const auto history = unread->data()->history(); + history->addUnreadBar(); if (hasPendingResizedItems()) { updateListSize(); } - if (_migrated->unreadBar) { - setMsgId(ShowAtUnreadMsgId); - result = countInitialScrollTop(); - App::wnd()->checkHistoryActivation(); - return result; - } - } - } else if (_history->showFrom) { - result = _list->itemTop(_history->showFrom); - if (result < _scroll->scrollTopMax() + HistoryMessageUnreadBar::height() - HistoryMessageUnreadBar::marginTop()) { - _history->addUnreadBar(); - if (hasPendingResizedItems()) { - updateListSize(); - } - if (_history->unreadBar) { + if (history->unreadBar() != nullptr) { setMsgId(ShowAtUnreadMsgId); result = countInitialScrollTop(); App::wnd()->checkHistoryActivation(); @@ -4792,7 +4783,23 @@ void HistoryWidget::updateHistoryGeometry(bool initial, bool loadedDown, const S updateListSize(); _updateHistoryGeometryRequired = false; - if ((!initial && !wasAtBottom) || (loadedDown && (!_history->showFrom || _history->unreadBar || _history->loadedAtBottom()) && (!_migrated || !_migrated->showFrom || _migrated->unreadBar || _history->loadedAtBottom()))) { + if ((!initial && !wasAtBottom) + || (loadedDown + && (!_history->firstUnreadMessage() + || _history->unreadBar() + || _history->loadedAtBottom()) + && (!_migrated + || !_migrated->firstUnreadMessage() + || _migrated->unreadBar() + || _history->loadedAtBottom()))) { + const auto historyScrollTop = _list->historyScrollTop(); + if (!wasAtBottom && historyScrollTop == ScrollMax) { + // History scroll top was not inited yet. + // If we're showing locally unread messages, we get here + // from destroyUnreadBar() before we have time to scroll + // to good initial position, like top of an unread bar. + return; + } auto toY = qMin(_list->historyScrollTop(), _scroll->scrollTopMax()); if (change.type == ScrollChangeAdd) { toY += change.value; @@ -4815,7 +4822,9 @@ void HistoryWidget::updateHistoryGeometry(bool initial, bool loadedDown, const S _historyInited = true; _scrollToAnimation.finish(); } - auto newScrollTop = initial ? countInitialScrollTop() : countAutomaticScrollTop(); + auto newScrollTop = initial + ? countInitialScrollTop() + : countAutomaticScrollTop(); if (_scroll->scrollTop() == newScrollTop) { visibleAreaUpdated(); } else { @@ -4841,25 +4850,33 @@ bool HistoryWidget::hasPendingResizedItems() const { || (_migrated && _migrated->hasPendingResizedItems()); } -int HistoryWidget::unreadBarTop() const { - auto getUnreadBar = [this]() -> HistoryItem* { - if (_migrated && _migrated->unreadBar) { - return _migrated->unreadBar; - } - if (_history->unreadBar) { - return _history->unreadBar; +base::optional HistoryWidget::unreadBarTop() const { + auto getUnreadBar = [this]() -> HistoryView::Element* { + if (const auto bar = _migrated ? _migrated->unreadBar() : nullptr) { + return bar; + } else if (const auto bar = _history->unreadBar()) { + return bar; } return nullptr; }; if (const auto bar = getUnreadBar()) { - auto result = _list->itemTop(bar) - + HistoryMessageUnreadBar::marginTop(); - if (bar->Has()) { - result += bar->Get()->height(); + const auto result = _list->itemTop(bar) + + HistoryView::UnreadBar::marginTop(); + if (bar->data()->Has()) { + return result + bar->data()->Get()->height(); } return result; } - return -1; + return base::none; +} + +HistoryView::Element *HistoryWidget::firstUnreadMessage() const { + if (_migrated) { + if (const auto result = _migrated->firstUnreadMessage()) { + return result; + } + } + return _history ? _history->firstUnreadMessage() : nullptr; } void HistoryWidget::addMessagesToFront(PeerData *peer, const QVector &messages) { @@ -4879,14 +4896,18 @@ void HistoryWidget::addMessagesToBack(PeerData *peer, const QVector } void HistoryWidget::countHistoryShowFrom() { - if (_migrated && _showAtMsgId == ShowAtUnreadMsgId && _migrated->unreadCount()) { - _migrated->updateShowFrom(); + if (_migrated + && _showAtMsgId == ShowAtUnreadMsgId + && _migrated->unreadCount()) { + _migrated->calculateFirstUnreadMessage(); } - if ((_migrated && _migrated->showFrom) || _showAtMsgId != ShowAtUnreadMsgId || !_history->unreadCount()) { - _history->showFrom = nullptr; - return; + if ((_migrated && _migrated->firstUnreadMessage()) + || (_showAtMsgId != ShowAtUnreadMsgId) + || !_history->unreadCount()) { + _history->unsetFirstUnreadMessage(); + } else { + _history->calculateFirstUnreadMessage(); } - _history->updateShowFrom(); } void HistoryWidget::updateBotKeyboard(History *h, bool force) { @@ -4989,26 +5010,30 @@ void HistoryWidget::updateHistoryDownPosition() { void HistoryWidget::updateHistoryDownVisibility() { if (_a_show.animating()) return; - auto haveUnreadBelowBottom = [this](History *history) { + auto haveUnreadBelowBottom = [&](History *history) { if (!_list || !history || history->unreadCount() <= 0) { return false; } - if (!history->showFrom || !history->showFrom->mainView()) { + const auto unread = history->firstUnreadMessage(); + if (!unread) { return false; } - return (_list->itemTop(history->showFrom) >= _scroll->scrollTop() + _scroll->height()); + const auto top = _list->itemTop(unread); + return (top >= _scroll->scrollTop() + _scroll->height()); }; - auto historyDownIsVisible = [this, &haveUnreadBelowBottom] { - if (!_history || _firstLoadRequest) { + auto historyDownIsVisible = [&] { + if (!_list || _firstLoadRequest) { return false; } if (!_history->loadedAtBottom() || _replyReturn) { return true; } - if (_scroll->scrollTop() + st::historyToDownShownAfter < _scroll->scrollTopMax()) { + const auto top = _scroll->scrollTop() + st::historyToDownShownAfter; + if (top < _scroll->scrollTopMax()) { return true; } - if (haveUnreadBelowBottom(_history) || haveUnreadBelowBottom(_migrated)) { + if (haveUnreadBelowBottom(_history) + || haveUnreadBelowBottom(_migrated)) { return true; } return false; @@ -5338,7 +5363,8 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() { updatePinnedBar(); result = true; - if (_scroll->scrollTop() != unreadBarTop()) { + const auto barTop = unreadBarTop(); + if (!barTop || _scroll->scrollTop() != *barTop) { synteticScrollToY(_scroll->scrollTop() + st::historyReplyHeight); } } else if (_pinnedBar->msgId != pinnedId) { @@ -5356,7 +5382,8 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() { } else if (_pinnedBar) { destroyPinnedBar(); result = true; - if (_scroll->scrollTop() != unreadBarTop()) { + const auto barTop = unreadBarTop(); + if (!barTop || _scroll->scrollTop() != *barTop) { synteticScrollToY(_scroll->scrollTop() - st::historyReplyHeight); } updateControlsGeometry(); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 8f3f1780c..117c427fe 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -203,7 +203,7 @@ public: void newUnreadMsg(History *history, HistoryItem *item); void historyToDown(History *history); - void unreadCountChanged(History *history); + void unreadCountChanged(not_null history); QRect historyRect() const; void pushTabbedSelectorToThirdSection( @@ -534,7 +534,6 @@ private: void updateTabbedSelectorToggleTooltipGeometry(); void checkTabbedSelectorToggleTooltip(); - bool historyHasNotFreezedUnreadBar(History *history) const; bool canWriteMessage() const; bool isRestrictedWrite() const; void orderWidgets(); @@ -670,9 +669,10 @@ private: // Counts scrollTop for placing the scroll right at the unread // messages bar, choosing from _history and _migrated unreadBar. - int unreadBarTop() const; + base::optional unreadBarTop() const; int itemTopForHighlight(not_null view) const; void scrollToCurrentVoiceMessage(FullMsgId fromId, FullMsgId toId); + HistoryView::Element *firstUnreadMessage() const; // Scroll to current y without updating the _lastUserScrolled time. // Used to distinguish between user scrolls and syntetic scrolls. diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index b31938808..d7be1641b 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -15,8 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_groups.h" #include "data/data_media_types.h" +#include "lang/lang_keys.h" #include "auth_session.h" #include "layout.h" +#include "styles/style_history.h" namespace HistoryView { namespace { @@ -54,6 +56,60 @@ TextSelection ShiftItemSelection( return ShiftItemSelection(selection, byText.length()); } +void UnreadBar::init(int count) { + if (freezed) { + return; + } + text = lng_unread_bar(lt_count, count); + width = st::semiboldFont->width(text); +} + +int UnreadBar::height() { + return st::historyUnreadBarHeight + st::historyUnreadBarMargin; +} + +int UnreadBar::marginTop() { + return st::lineWidth + st::historyUnreadBarMargin; +} + +void UnreadBar::paint(Painter &p, int y, int w) const { + const auto bottom = y + height(); + y += marginTop(); + p.fillRect( + 0, + y, + w, + height() - marginTop() - st::lineWidth, + st::historyUnreadBarBg); + p.fillRect( + 0, + bottom - st::lineWidth, + w, + st::lineWidth, + st::historyUnreadBarBorder); + p.setFont(st::historyUnreadBarFont); + p.setPen(st::historyUnreadBarFg); + + int left = st::msgServiceMargin.left(); + int maxwidth = w; + if (Adaptive::ChatWide()) { + maxwidth = qMin( + maxwidth, + st::msgMaxWidth + + 2 * st::msgPhotoSkip + + 2 * st::msgMargin.left()); + } + w = maxwidth; + + const auto skip = st::historyUnreadBarHeight + - 2 * st::lineWidth + - st::historyUnreadBarFont->height; + p.drawText( + (w - width) / 2, + y + (skip / 2) + st::historyUnreadBarFont->ascent, + text); +} + Element::Element( not_null delegate, not_null data) @@ -99,8 +155,8 @@ int Element::marginTop() const { } } result += item->displayedDateHeight(); - if (const auto unreadbar = item->Get()) { - result += unreadbar->height(); + if (const auto bar = Get()) { + result += bar->height(); } return result; } @@ -198,7 +254,7 @@ void Element::refreshDataId() { bool Element::computeIsAttachToPrevious(not_null previous) { const auto item = data(); - if (!item->Has() && !item->Has()) { + if (!item->Has() && !Has()) { const auto prev = previous->data(); const auto possible = !item->serviceMsg() && !prev->serviceMsg() && !item->isEmpty() && !prev->isEmpty() @@ -217,6 +273,42 @@ bool Element::computeIsAttachToPrevious(not_null previous) { return false; } +void Element::destroyUnreadBar() { + if (!Has()) { + return; + } + RemoveComponents(UnreadBar::Bit()); + Auth().data().requestViewResize(this); + if (data()->mainView() == this) { + recountAttachToPreviousInBlocks(); + } +} + +void Element::setUnreadBarCount(int count) { + Expects(count > 0); + + const auto changed = AddComponents(UnreadBar::Bit()); + const auto bar = Get(); + if (bar->freezed) { + return; + } + bar->init(count); + if (changed) { + if (data()->mainView() == this) { + recountAttachToPreviousInBlocks(); + } + Auth().data().requestViewResize(this); + } else { + Auth().data().requestViewRepaint(this); + } +} + +void Element::setUnreadBarFreezed() { + if (const auto bar = Get()) { + bar->freezed = true; + } +} + void Element::recountAttachToPreviousInBlocks() { auto attachToPrevious = false; if (const auto previous = previousInBlocks()) { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 6d7a5096f..cf2e9233e 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -55,6 +55,29 @@ TextSelection ShiftItemSelection( TextSelection selection, const Text &byText); +// Any HistoryView::Element can have this Component for +// displaying the unread messages bar above the message. +struct UnreadBar : public RuntimeComponent { + void init(int count); + + static int height(); + static int marginTop(); + + void paint(Painter &p, int y, int w) const; + + QString text; + int width = 0; + + // If unread bar is freezed the new messages do not + // increment the counter displayed by this bar. + // + // It happens when we've opened the conversation and + // we've seen the bar and new messages are marked as read + // as soon as they are added to the chat history. + bool freezed = false; + +}; + class Element : public Object , public RuntimeComposer @@ -110,6 +133,16 @@ public: bool computeIsAttachToPrevious(not_null previous); + // count > 0 - creates the unread bar if necessary and + // sets unread messages count if bar is not freezed yet + void setUnreadBarCount(int count); + void destroyUnreadBar(); + + // marks the unread bar as freezed so that unread + // messages count will not change for this bar + // when the new messages arrive in this chat history + void setUnreadBarFreezed(); + virtual void draw( Painter &p, QRect clip, @@ -195,7 +228,7 @@ private: void recountDisplayDateInBlocks(); // This should be called only from previousInBlocksChanged() or when - // HistoryMessageDate or HistoryMessageUnreadBar bit is changed in the Composer mask + // HistoryMessageDate or UnreadBar bit is changed in the Composer mask // then the result should be cached in a client side flag MTPDmessage_ClientFlag::f_attach_to_previous. void recountAttachToPreviousInBlocks(); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index e176ece3a..7ca133fd3 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -231,7 +231,7 @@ ListWidget::ListWidget( Auth().data().viewResizeRequest( ) | rpl::start_with_next([this](auto view) { if (view->delegate() == this) { - updateSize(); + resizeItem(view); } }, lifetime()); Auth().data().itemViewRefreshRequest( @@ -572,24 +572,7 @@ void ListWidget::updateItemsGeometry() { } return count; }(); - if (first < count) { - auto view = _items[first].get(); - for (auto i = first + 1; i != count; ++i) { - const auto next = _items[i].get(); - if (next->isHiddenByGroup()) { - next->setDisplayDate(false); - } else { - const auto viewDate = view->data()->date; - const auto nextDate = next->data()->date; - next->setDisplayDate(nextDate.date() != viewDate.date()); - auto attached = next->computeIsAttachToPrevious(view); - next->setAttachToPrevious(attached); - view->setAttachToNext(attached); - view = next; - } - } - } - updateSize(); + refreshAttachmentsFromTill(first, count); } void ListWidget::updateSize() { @@ -1452,6 +1435,61 @@ void ListWidget::repaintItem(const Element *view) { update(0, itemTop(view), width(), view->height()); } +void ListWidget::resizeItem(not_null view) { + const auto index = ranges::find(_items, view) - begin(_items); + if (index < int(_items.size())) { + refreshAttachmentsAtIndex(index); + } +} + +void ListWidget::refreshAttachmentsAtIndex(int index) { + Expects(index >= 0 && index < _items.size()); + + const auto from = [&] { + if (index > 0) { + for (auto i = index - 1; i != 0; --i) { + if (!_items[i]->isHiddenByGroup()) { + return i; + } + } + } + return index; + }(); + const auto till = [&] { + for (auto i = index + 1, count = int(_items.size()); i != count; ) { + if (!_items[i]->isHiddenByGroup()) { + return i + 1; + } + } + return index + 1; + }(); + refreshAttachmentsFromTill(from, till); +} + +void ListWidget::refreshAttachmentsFromTill(int from, int till) { + Expects(from >= 0 && from <= till && till <= int(_items.size())); + + if (from == till) { + return; + } + auto view = _items[from].get(); + for (auto i = from + 1; i != till; ++i) { + const auto next = _items[i].get(); + if (next->isHiddenByGroup()) { + next->setDisplayDate(false); + } else { + const auto viewDate = view->data()->date; + const auto nextDate = next->data()->date; + next->setDisplayDate(nextDate.date() != viewDate.date()); + auto attached = next->computeIsAttachToPrevious(view); + next->setAttachToPrevious(attached); + view->setAttachToNext(attached); + view = next; + } + } + updateSize(); +} + void ListWidget::refreshItem(not_null view) { const auto i = ranges::find(_items, view); const auto index = i - begin(_items); @@ -1467,7 +1505,7 @@ void ListWidget::refreshItem(not_null view) { viewReplaced(view, i->second.get()); - updateItemsGeometry(); + refreshAttachmentsAtIndex(index); } } diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index c88d527b6..dd22ab1a2 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -165,6 +165,7 @@ private: void performDrag(); int itemTop(not_null view) const; void repaintItem(const Element *view); + void resizeItem(not_null view); void refreshItem(not_null view); void itemRemoved(not_null item); QPoint mapPointToItem(QPoint point, const Element *view) const; @@ -194,6 +195,8 @@ private: void updateVisibleTopItem(); void updateItemsGeometry(); void updateSize(); + void refreshAttachmentsFromTill(int from, int till); + void refreshAttachmentsAtIndex(int index); void toggleScrollDateShown(); void repaintScrollDateCallback(); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 7031a0dcd..5bea47ab1 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -360,14 +360,14 @@ void Message::draw( } auto dateh = 0; - if (auto date = item->Get()) { + if (const auto date = item->Get()) { dateh = date->height(); } - if (auto unreadbar = item->Get()) { - auto unreadbarh = unreadbar->height(); + if (const auto bar = Get()) { + auto unreadbarh = bar->height(); if (clip.intersects(QRect(0, dateh, width(), unreadbarh))) { p.translate(0, dateh); - unreadbar->paint(p, 0, width()); + bar->paint(p, 0, width()); p.translate(0, -dateh); } } diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index 33c2f4554..c34b3a270 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -315,8 +315,8 @@ QSize Service::performCountCurrentSize(int newWidth) { const auto media = this->media(); auto newHeight = item->displayedDateHeight(); - if (auto unreadbar = item->Get()) { - newHeight += unreadbar->height(); + if (const auto bar = Get()) { + newHeight += bar->height(); } if (item->_text.isEmpty()) { @@ -382,10 +382,10 @@ void Service::draw( clip.translate(0, -dateh); height -= dateh; } - if (auto unreadbar = item->Get()) { - unreadbarh = unreadbar->height(); + if (const auto bar = Get()) { + unreadbarh = bar->height(); if (clip.intersects(QRect(0, 0, width(), unreadbarh))) { - unreadbar->paint(p, 0, width()); + bar->paint(p, 0, width()); } p.translate(0, unreadbarh); clip.translate(0, -unreadbarh); @@ -405,7 +405,7 @@ void Service::draw( auto dt = (animms > st::activeFadeInDuration) ? (1. - (animms - st::activeFadeInDuration) / float64(st::activeFadeOutDuration)) : (animms / float64(st::activeFadeInDuration)); auto o = p.opacity(); p.setOpacity(o * dt); - p.fillRect(0, skiptop, item->history()->width, fillheight, st::defaultTextPalette.selectOverlay); + p.fillRect(0, skiptop, width(), fillheight, st::defaultTextPalette.selectOverlay); p.setOpacity(o); } } @@ -448,8 +448,8 @@ bool Service::hasPoint(QPoint point) const { if (auto dateh = item->displayedDateHeight()) { g.setTop(g.top() + dateh); } - if (auto unreadbar = item->Get()) { - g.setTop(g.top() + unreadbar->height()); + if (const auto bar = Get()) { + g.setTop(g.top() + bar->height()); } if (media) { g.setHeight(g.height() - (st::msgServiceMargin.top() + media->height())); @@ -472,8 +472,8 @@ HistoryTextState Service::getState(QPoint point, HistoryStateRequest request) co point.setY(point.y() - dateh); g.setHeight(g.height() - dateh); } - if (auto unreadbar = item->Get()) { - auto unreadbarh = unreadbar->height(); + if (const auto bar = Get()) { + auto unreadbarh = bar->height(); point.setY(point.y() - unreadbarh); g.setHeight(g.height() - unreadbarh); } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index e2981f1db..71a752d4a 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1516,7 +1516,7 @@ void MainWidget::saveRecentHashtags(const QString &text) { } } -void MainWidget::unreadCountChanged(History *history) { +void MainWidget::unreadCountChanged(not_null history) { _history->unreadCountChanged(history); } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index c0d73ea27..96708a033 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -242,7 +242,7 @@ public: void sendMessage(const MessageToSend &message); void saveRecentHashtags(const QString &text); - void unreadCountChanged(History *history); + void unreadCountChanged(not_null history); TimeMs highlightStartTime(not_null item) const;