From 7bbc4b7191fad1054fa64de04ee1999bd5048d05 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 19 Jul 2021 13:02:36 +0300 Subject: [PATCH] Add basic new messages animation. --- Telegram/SourceFiles/data/data_session.cpp | 8 +- Telegram/SourceFiles/data/data_session.h | 6 +- Telegram/SourceFiles/history/history.cpp | 20 +- .../history/history_inner_widget.cpp | 21 +- .../history/history_inner_widget.h | 3 + .../SourceFiles/history/history_widget.cpp | 193 ++++++++++++------ Telegram/SourceFiles/history/history_widget.h | 16 +- .../history/view/history_view_list_widget.cpp | 2 +- 8 files changed, 189 insertions(+), 80 deletions(-) diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 4fcdb3e49..1f0fedb4d 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1441,12 +1441,12 @@ rpl::producer> Session::viewLayoutChanged() const { return _viewLayoutChanges.events(); } -void Session::notifyUnreadItemAdded(not_null item) { - _unreadItemAdded.fire_copy(item); +void Session::notifyNewItemAdded(not_null item) { + _newItemAdded.fire_copy(item); } -rpl::producer> Session::unreadItemAdded() const { - return _unreadItemAdded.events(); +rpl::producer> Session::newItemAdded() const { + return _newItemAdded.events(); } void Session::changeMessageId(ChannelId channel, MsgId wasId, MsgId nowId) { diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 45547f46d..fa3c50409 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -235,8 +235,8 @@ public: [[nodiscard]] rpl::producer> itemLayoutChanged() const; void notifyViewLayoutChange(not_null view); [[nodiscard]] rpl::producer> viewLayoutChanged() const; - void notifyUnreadItemAdded(not_null item); - [[nodiscard]] rpl::producer> unreadItemAdded() const; + void notifyNewItemAdded(not_null item); + [[nodiscard]] rpl::producer> newItemAdded() const; void requestItemRepaint(not_null item); [[nodiscard]] rpl::producer> itemRepaintRequest() const; void requestViewRepaint(not_null view); @@ -836,7 +836,7 @@ private: rpl::event_stream _itemIdChanges; rpl::event_stream> _itemLayoutChanges; rpl::event_stream> _viewLayoutChanges; - rpl::event_stream> _unreadItemAdded; + rpl::event_stream> _newItemAdded; rpl::event_stream> _itemRepaintRequest; rpl::event_stream> _viewRepaintRequest; rpl::event_stream> _itemResizeRequest; diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index e5028c1db..2fc24ea98 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1072,16 +1072,16 @@ void History::newItemAdded(not_null item) { item->contributeToSlowmode(); if (item->showNotification()) { _notifications.push_back(item); - owner().notifyUnreadItemAdded(item); - const auto stillShow = item->showNotification(); - if (stillShow) { - Core::App().notifications().schedule(item); - if (!item->out() && item->unread()) { - if (unreadCountKnown()) { - setUnreadCount(unreadCount() + 1); - } else { - owner().histories().requestDialogEntry(this); - } + } + owner().notifyNewItemAdded(item); + const auto stillShow = item->showNotification(); // Could be read already. + if (stillShow) { + Core::App().notifications().schedule(item); + if (!item->out() && item->unread()) { + if (unreadCountKnown()) { + setUnreadCount(unreadCount() + 1); + } else { + owner().histories().requestDialogEntry(this); } } } else if (item->out()) { diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index a351ec98b..42337c014 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2369,6 +2369,21 @@ void HistoryInner::repaintScrollDateCallback() { update(0, updateTop, width(), updateHeight); } +void HistoryInner::setItemsRevealHeight(int revealHeight) { + _revealHeight = revealHeight; +} + +void HistoryInner::changeItemsRevealHeight(int revealHeight) { + if (_revealHeight == revealHeight) { + return; + } + const auto old = std::exchange(_revealHeight, revealHeight); + resize(_scroll->width(), height() + old - _revealHeight); + if (!_revealHeight) { + mouseActionUpdate(QCursor::pos()); + } +} + void HistoryInner::updateSize() { int visibleHeight = _scroll->height(); int newHistoryPaddingTop = qMax(visibleHeight - historyHeight() - st::historyPaddingBottom, 0); @@ -2393,11 +2408,13 @@ void HistoryInner::updateSize() { _historyPaddingTop = newHistoryPaddingTop; - int newHeight = _historyPaddingTop + historyHeight() + st::historyPaddingBottom; + int newHeight = _historyPaddingTop + historyHeight() + st::historyPaddingBottom - _revealHeight; if (width() != _scroll->width() || height() != newHeight) { resize(_scroll->width(), newHeight); - mouseActionUpdate(QCursor::pos()); + if (!_revealHeight) { + mouseActionUpdate(QCursor::pos()); + } } else { update(); } diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 67eb4206b..e5304749c 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -64,6 +64,8 @@ public: void touchScrollUpdated(const QPoint &screenPos); + void setItemsRevealHeight(int revealHeight); + void changeItemsRevealHeight(int revealHeight); void checkHistoryActivation(); void recountHistoryGeometry(); void updateSize(); @@ -345,6 +347,7 @@ private: History *_migrated = nullptr; int _contentWidth = 0; int _historyPaddingTop = 0; + int _revealHeight = 0; // Save visible area coords for painting / pressing userpics. int _visibleAreaTop = 0; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 29660f306..53ba504b0 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -154,6 +154,7 @@ constexpr auto kSaveDraftTimeout = 1000; constexpr auto kSaveDraftAnywayTimeout = 5000; constexpr auto kSaveCloudDraftIdleTimeout = 14000; constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200); +constexpr auto kItemRevealDuration = crl::time(200); constexpr auto kCommonModifiers = 0 | Qt::ShiftModifier | Qt::MetaModifier @@ -425,14 +426,9 @@ HistoryWidget::HistoryWidget( } }, lifetime()); - session().data().unreadItemAdded( + session().data().newItemAdded( ) | rpl::start_with_next([=](not_null item) { - unreadMessageAdded(item); - }, lifetime()); - - session().data().itemRemoved( - ) | rpl::start_with_next([=](not_null item) { - itemRemoved(item); + newItemAdded(item); }, lifetime()); session().data().historyChanged( @@ -539,43 +535,25 @@ HistoryWidget::HistoryWidget( }, lifetime()); session().changes().messageUpdates( - Data::MessageUpdate::Flag::Edited + Data::MessageUpdate::Flag::Destroyed + | Data::MessageUpdate::Flag::Edited + | Data::MessageUpdate::Flag::ReplyMarkup + | Data::MessageUpdate::Flag::BotCallbackSent ) | rpl::start_with_next([=](const Data::MessageUpdate &update) { - itemEdited(update.item); - }, lifetime()); - - session().changes().messageUpdates( - Data::MessageUpdate::Flag::ReplyMarkup - ) | rpl::start_with_next([=](const Data::MessageUpdate &update) { - if (_keyboard->forMsgId() == update.item->fullId()) { - updateBotKeyboard(update.item->history(), true); - } - }, lifetime()); - - session().changes().messageUpdates( - Data::MessageUpdate::Flag::BotCallbackSent - ) | rpl::start_with_next([=](const Data::MessageUpdate &update) { - const auto item = update.item; - if (item->id < 0 || _peer != item->history()->peer) { + if (update.flags & Data::MessageUpdate::Flag::Destroyed) { + itemRemoved(update.item); return; } - - const auto keyId = _keyboard->forMsgId(); - const auto lastKeyboardUsed = (keyId == FullMsgId(_channel, item->id)) - && (keyId == FullMsgId(_channel, _history->lastKeyboardId)); - - session().data().requestItemRepaint(item); - - if (_replyToId == item->id) { - cancelReply(); + if (update.flags & Data::MessageUpdate::Flag::Edited) { + itemEdited(update.item); } - if (_keyboard->singleUse() - && _keyboard->hasMarkup() - && lastKeyboardUsed) { - if (_kbShown) { - toggleKeyboard(false); + if (update.flags & Data::MessageUpdate::Flag::ReplyMarkup) { + if (_keyboard->forMsgId() == update.item->fullId()) { + updateBotKeyboard(update.item->history(), true); } - _history->lastKeyboardUsed = true; + } + if (update.flags & Data::MessageUpdate::Flag::BotCallbackSent) { + botCallbackSent(update.item); } }, lifetime()); @@ -1887,7 +1865,6 @@ void HistoryWidget::showHistory( App::clearMousedItems(); - _addToScroll = 0; _saveEditMsgRequestId = 0; _replyEditMsg = nullptr; _editMsgId = _replyToId = 0; @@ -1939,6 +1916,7 @@ void HistoryWidget::showHistory( noSelectingScroll(); _nonEmptySelection = false; + _itemRevealPending.clear(); if (_peer) { _history = _peer->owner().history(_peer); @@ -2447,8 +2425,10 @@ void HistoryWidget::destroyUnreadBarOnClose() { } } -void HistoryWidget::unreadMessageAdded(not_null item) { - if (_history != item->history() || !_historyInited) { +void HistoryWidget::newItemAdded(not_null item) { + if (_history != item->history() + || !_historyInited + || item->isScheduled()) { return; } @@ -2459,21 +2439,28 @@ void HistoryWidget::unreadMessageAdded(not_null item) { // - on second we get wrong doWeReadServerHistory() and read both. session().data().sendHistoryChangeNotifications(); - const auto atBottom = (_scroll->scrollTop() >= _scroll->scrollTopMax()); - if (!atBottom) { + if (item->isSending()) { + synteticScrollToY(_scroll->scrollTopMax()); + } else if (_scroll->scrollTop() < _scroll->scrollTopMax()) { return; } - destroyUnreadBar(); - if (!doWeReadServerHistory()) { - return; - } - if (item->isUnreadMention() && !item->isUnreadMedia()) { - session().api().markMediaRead(item); - } - session().data().histories().readInboxOnNewMessage(item); + if (item->showNotification()) { + destroyUnreadBar(); + if (doWeReadServerHistory()) { + if (item->isUnreadMention() && !item->isUnreadMedia()) { + session().api().markMediaRead(item); + } + session().data().histories().readInboxOnNewMessage(item); - // Also clear possible scheduled messages notifications. - Core::App().notifications().clearFromHistory(_history); + // Also clear possible scheduled messages notifications. + Core::App().notifications().clearFromHistory(_history); + } + } + const auto view = item->mainView(); + if (anim::Disabled() || !view) { + return; + } + _itemRevealPending.emplace(item); } void HistoryWidget::unreadCountUpdated() { @@ -2904,9 +2891,13 @@ void HistoryWidget::delayedShowAt(MsgId showAtMsgId) { } void HistoryWidget::handleScroll() { - preloadHistoryIfNeeded(); + if (!_itemsRevealHeight) { + preloadHistoryIfNeeded(); + } visibleAreaUpdated(); - updatePinnedViewer(); + if (!_itemsRevealHeight) { + updatePinnedViewer(); + } if (!_synteticScrollEvent) { _lastUserScrolled = crl::now(); } @@ -4692,6 +4683,11 @@ void HistoryWidget::itemRemoved(not_null item) { updateControlsGeometry(); } } + const auto i = _itemRevealAnimations.find(item); + if (i != end(_itemRevealAnimations)) { + _itemRevealAnimations.erase(i); + revealItemsCallback(); + } } void HistoryWidget::itemEdited(not_null item) { @@ -4784,6 +4780,9 @@ void HistoryWidget::updateHistoryGeometry( bool initial, bool loadedDown, const ScrollChange &change) { + const auto guard = gsl::finally([&] { + _itemRevealPending.clear(); + }); if (!_history || (initial && _historyInited) || (!initial && !_historyInited)) { return; } @@ -4867,20 +4866,74 @@ void HistoryWidget::updateHistoryGeometry( newScrollTop += change.value; } else if (change.type == ScrollChangeNoJumpToBottom) { newScrollTop = wasScrollTop; - } else if (const auto add = base::take(_addToScroll)) { - newScrollTop += add; } } const auto toY = std::clamp(newScrollTop, 0, _scroll->scrollTopMax()); synteticScrollToY(toY); } +void HistoryWidget::revealItemsCallback() { + auto height = 0; + if (!_historyInited) { + _itemRevealAnimations.clear(); + } + for (auto i = begin(_itemRevealAnimations) + ; i != end(_itemRevealAnimations);) { + if (!i->second.animation.animating()) { + i = _itemRevealAnimations.erase(i); + } else { + height += anim::interpolate( + i->second.startHeight, + 0, + i->second.animation.value(1.)); + ++i; + } + } + if (_itemsRevealHeight != height) { + const auto wasScrollTop = _scroll->scrollTop(); + const auto wasAtBottom = (wasScrollTop == _scroll->scrollTopMax()); + + _itemsRevealHeight = height; + _list->changeItemsRevealHeight(_itemsRevealHeight); + + const auto newScrollTop = (wasAtBottom && !_history->unreadBar()) + ? countAutomaticScrollTop() + : _list->historyScrollTop(); + const auto toY = std::clamp(newScrollTop, 0, _scroll->scrollTopMax()); + synteticScrollToY(toY); + } +} + void HistoryWidget::updateListSize() { _list->recountHistoryGeometry(); auto washidden = _scroll->isHidden(); if (washidden) { _scroll->show(); } + for (const auto item : base::take(_itemRevealPending)) { + if (const auto view = item->mainView()) { + if (const auto top = _list->itemTop(view); top >= 0) { + if (const auto height = view->height()) { + if (!_itemRevealAnimations.contains(item)) { + auto &animation = _itemRevealAnimations[item]; + if (!animation.animation.animating()) { + animation.startHeight + = animation.currentHeight + = height; + _itemsRevealHeight += height; + animation.animation.start( + [=] { revealItemsCallback(); }, + 0., + 1., + kItemRevealDuration, + anim::easeOutCirc); + } + } + } + } + } + } + _list->setItemsRevealHeight(_itemsRevealHeight); _list->updateSize(); if (washidden) { _scroll->hide(); @@ -5029,6 +5082,30 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { update(); } +void HistoryWidget::botCallbackSent(not_null item) { + if (item->id < 0 || _peer != item->history()->peer) { + return; + } + + const auto keyId = _keyboard->forMsgId(); + const auto lastKeyboardUsed = (keyId == FullMsgId(_channel, item->id)) + && (keyId == FullMsgId(_channel, _history->lastKeyboardId)); + + session().data().requestItemRepaint(item); + + if (_replyToId == item->id) { + cancelReply(); + } + if (_keyboard->singleUse() + && _keyboard->hasMarkup() + && lastKeyboardUsed) { + if (_kbShown) { + toggleKeyboard(false); + } + _history->lastKeyboardUsed = true; + } +} + int HistoryWidget::computeMaxFieldHeight() const { const auto available = height() - _topBar->height() diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 99e24905e..b298bd204 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -225,6 +225,7 @@ public: // With force=true the markup is updated even if it is // already shown for the passed history item. void updateBotKeyboard(History *h = nullptr, bool force = false); + void botCallbackSent(not_null item); void fastShowAtEnd(not_null history); void applyDraft( @@ -321,6 +322,11 @@ private: Fn callback; bool active = false; }; + struct ItemRevealAnimation { + int startHeight = 0; + int currentHeight = 0; + Ui::Animations::Simple animation; + }; enum class TextUpdateEvent { SaveDraft = (1 << 0), SendTyping = (1 << 1), @@ -414,7 +420,7 @@ private: void historyDownAnimationFinish(); void unreadMentionsAnimationFinish(); void sendButtonClicked(); - void unreadMessageAdded(not_null item); + void newItemAdded(not_null item); bool canSendFiles(not_null data) const; bool confirmSendingFiles( @@ -530,6 +536,7 @@ private: void updateHistoryGeometry(bool initial = false, bool loadedDown = false, const ScrollChange &change = { ScrollChangeNone, 0 }); void updateListSize(); + void revealItemsCallback(); // Does any of the shown histories has this flag set. bool hasPendingResizedItems() const; @@ -664,7 +671,6 @@ private: bool _historyInited = false; // If updateListSize() was called without updateHistoryGeometry(). bool _updateHistoryGeometryRequired = false; - int _addToScroll = 0; int _lastScrollTop = 0; // gifs optimization crl::time _lastScrolled = 0; @@ -755,6 +761,12 @@ private: base::weak_ptr _topToast; std::unique_ptr _chooseForReport; + base::flat_set> _itemRevealPending; + base::flat_map< + not_null, + ItemRevealAnimation> _itemRevealAnimations; + int _itemsRevealHeight = 0; + object_ptr _topShadow; bool _inGrab = false; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 33f979bdd..e1be2af7c 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -1124,7 +1124,7 @@ bool ListWidget::loadedAtBottom() const { } bool ListWidget::isEmpty() const { - return loadedAtTop() && loadedAtBottom() && (_itemsHeight == 0); + return loadedAtTop() && loadedAtBottom(); } int ListWidget::itemMinimalHeight() const {