Add basic new messages animation.

This commit is contained in:
John Preston 2021-07-19 13:02:36 +03:00
parent 551732738b
commit 7bbc4b7191
8 changed files with 189 additions and 80 deletions

View file

@ -1441,12 +1441,12 @@ rpl::producer<not_null<const ViewElement*>> Session::viewLayoutChanged() const {
return _viewLayoutChanges.events(); return _viewLayoutChanges.events();
} }
void Session::notifyUnreadItemAdded(not_null<HistoryItem*> item) { void Session::notifyNewItemAdded(not_null<HistoryItem*> item) {
_unreadItemAdded.fire_copy(item); _newItemAdded.fire_copy(item);
} }
rpl::producer<not_null<HistoryItem*>> Session::unreadItemAdded() const { rpl::producer<not_null<HistoryItem*>> Session::newItemAdded() const {
return _unreadItemAdded.events(); return _newItemAdded.events();
} }
void Session::changeMessageId(ChannelId channel, MsgId wasId, MsgId nowId) { void Session::changeMessageId(ChannelId channel, MsgId wasId, MsgId nowId) {

View file

@ -235,8 +235,8 @@ public:
[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemLayoutChanged() const; [[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemLayoutChanged() const;
void notifyViewLayoutChange(not_null<const ViewElement*> view); void notifyViewLayoutChange(not_null<const ViewElement*> view);
[[nodiscard]] rpl::producer<not_null<const ViewElement*>> viewLayoutChanged() const; [[nodiscard]] rpl::producer<not_null<const ViewElement*>> viewLayoutChanged() const;
void notifyUnreadItemAdded(not_null<HistoryItem*> item); void notifyNewItemAdded(not_null<HistoryItem*> item);
[[nodiscard]] rpl::producer<not_null<HistoryItem*>> unreadItemAdded() const; [[nodiscard]] rpl::producer<not_null<HistoryItem*>> newItemAdded() const;
void requestItemRepaint(not_null<const HistoryItem*> item); void requestItemRepaint(not_null<const HistoryItem*> item);
[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemRepaintRequest() const; [[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemRepaintRequest() const;
void requestViewRepaint(not_null<const ViewElement*> view); void requestViewRepaint(not_null<const ViewElement*> view);
@ -836,7 +836,7 @@ private:
rpl::event_stream<IdChange> _itemIdChanges; rpl::event_stream<IdChange> _itemIdChanges;
rpl::event_stream<not_null<const HistoryItem*>> _itemLayoutChanges; rpl::event_stream<not_null<const HistoryItem*>> _itemLayoutChanges;
rpl::event_stream<not_null<const ViewElement*>> _viewLayoutChanges; rpl::event_stream<not_null<const ViewElement*>> _viewLayoutChanges;
rpl::event_stream<not_null<HistoryItem*>> _unreadItemAdded; rpl::event_stream<not_null<HistoryItem*>> _newItemAdded;
rpl::event_stream<not_null<const HistoryItem*>> _itemRepaintRequest; rpl::event_stream<not_null<const HistoryItem*>> _itemRepaintRequest;
rpl::event_stream<not_null<const ViewElement*>> _viewRepaintRequest; rpl::event_stream<not_null<const ViewElement*>> _viewRepaintRequest;
rpl::event_stream<not_null<const HistoryItem*>> _itemResizeRequest; rpl::event_stream<not_null<const HistoryItem*>> _itemResizeRequest;

View file

@ -1072,16 +1072,16 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
item->contributeToSlowmode(); item->contributeToSlowmode();
if (item->showNotification()) { if (item->showNotification()) {
_notifications.push_back(item); _notifications.push_back(item);
owner().notifyUnreadItemAdded(item); }
const auto stillShow = item->showNotification(); owner().notifyNewItemAdded(item);
if (stillShow) { const auto stillShow = item->showNotification(); // Could be read already.
Core::App().notifications().schedule(item); if (stillShow) {
if (!item->out() && item->unread()) { Core::App().notifications().schedule(item);
if (unreadCountKnown()) { if (!item->out() && item->unread()) {
setUnreadCount(unreadCount() + 1); if (unreadCountKnown()) {
} else { setUnreadCount(unreadCount() + 1);
owner().histories().requestDialogEntry(this); } else {
} owner().histories().requestDialogEntry(this);
} }
} }
} else if (item->out()) { } else if (item->out()) {

View file

@ -2369,6 +2369,21 @@ void HistoryInner::repaintScrollDateCallback() {
update(0, updateTop, width(), updateHeight); 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() { void HistoryInner::updateSize() {
int visibleHeight = _scroll->height(); int visibleHeight = _scroll->height();
int newHistoryPaddingTop = qMax(visibleHeight - historyHeight() - st::historyPaddingBottom, 0); int newHistoryPaddingTop = qMax(visibleHeight - historyHeight() - st::historyPaddingBottom, 0);
@ -2393,11 +2408,13 @@ void HistoryInner::updateSize() {
_historyPaddingTop = newHistoryPaddingTop; _historyPaddingTop = newHistoryPaddingTop;
int newHeight = _historyPaddingTop + historyHeight() + st::historyPaddingBottom; int newHeight = _historyPaddingTop + historyHeight() + st::historyPaddingBottom - _revealHeight;
if (width() != _scroll->width() || height() != newHeight) { if (width() != _scroll->width() || height() != newHeight) {
resize(_scroll->width(), newHeight); resize(_scroll->width(), newHeight);
mouseActionUpdate(QCursor::pos()); if (!_revealHeight) {
mouseActionUpdate(QCursor::pos());
}
} else { } else {
update(); update();
} }

View file

@ -64,6 +64,8 @@ public:
void touchScrollUpdated(const QPoint &screenPos); void touchScrollUpdated(const QPoint &screenPos);
void setItemsRevealHeight(int revealHeight);
void changeItemsRevealHeight(int revealHeight);
void checkHistoryActivation(); void checkHistoryActivation();
void recountHistoryGeometry(); void recountHistoryGeometry();
void updateSize(); void updateSize();
@ -345,6 +347,7 @@ private:
History *_migrated = nullptr; History *_migrated = nullptr;
int _contentWidth = 0; int _contentWidth = 0;
int _historyPaddingTop = 0; int _historyPaddingTop = 0;
int _revealHeight = 0;
// Save visible area coords for painting / pressing userpics. // Save visible area coords for painting / pressing userpics.
int _visibleAreaTop = 0; int _visibleAreaTop = 0;

View file

@ -154,6 +154,7 @@ constexpr auto kSaveDraftTimeout = 1000;
constexpr auto kSaveDraftAnywayTimeout = 5000; constexpr auto kSaveDraftAnywayTimeout = 5000;
constexpr auto kSaveCloudDraftIdleTimeout = 14000; constexpr auto kSaveCloudDraftIdleTimeout = 14000;
constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200); constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200);
constexpr auto kItemRevealDuration = crl::time(200);
constexpr auto kCommonModifiers = 0 constexpr auto kCommonModifiers = 0
| Qt::ShiftModifier | Qt::ShiftModifier
| Qt::MetaModifier | Qt::MetaModifier
@ -425,14 +426,9 @@ HistoryWidget::HistoryWidget(
} }
}, lifetime()); }, lifetime());
session().data().unreadItemAdded( session().data().newItemAdded(
) | rpl::start_with_next([=](not_null<HistoryItem*> item) { ) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
unreadMessageAdded(item); newItemAdded(item);
}, lifetime());
session().data().itemRemoved(
) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
itemRemoved(item);
}, lifetime()); }, lifetime());
session().data().historyChanged( session().data().historyChanged(
@ -539,43 +535,25 @@ HistoryWidget::HistoryWidget(
}, lifetime()); }, lifetime());
session().changes().messageUpdates( 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) { ) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
itemEdited(update.item); if (update.flags & Data::MessageUpdate::Flag::Destroyed) {
}, lifetime()); itemRemoved(update.item);
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) {
return; return;
} }
if (update.flags & Data::MessageUpdate::Flag::Edited) {
const auto keyId = _keyboard->forMsgId(); itemEdited(update.item);
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() if (update.flags & Data::MessageUpdate::Flag::ReplyMarkup) {
&& _keyboard->hasMarkup() if (_keyboard->forMsgId() == update.item->fullId()) {
&& lastKeyboardUsed) { updateBotKeyboard(update.item->history(), true);
if (_kbShown) {
toggleKeyboard(false);
} }
_history->lastKeyboardUsed = true; }
if (update.flags & Data::MessageUpdate::Flag::BotCallbackSent) {
botCallbackSent(update.item);
} }
}, lifetime()); }, lifetime());
@ -1887,7 +1865,6 @@ void HistoryWidget::showHistory(
App::clearMousedItems(); App::clearMousedItems();
_addToScroll = 0;
_saveEditMsgRequestId = 0; _saveEditMsgRequestId = 0;
_replyEditMsg = nullptr; _replyEditMsg = nullptr;
_editMsgId = _replyToId = 0; _editMsgId = _replyToId = 0;
@ -1939,6 +1916,7 @@ void HistoryWidget::showHistory(
noSelectingScroll(); noSelectingScroll();
_nonEmptySelection = false; _nonEmptySelection = false;
_itemRevealPending.clear();
if (_peer) { if (_peer) {
_history = _peer->owner().history(_peer); _history = _peer->owner().history(_peer);
@ -2447,8 +2425,10 @@ void HistoryWidget::destroyUnreadBarOnClose() {
} }
} }
void HistoryWidget::unreadMessageAdded(not_null<HistoryItem*> item) { void HistoryWidget::newItemAdded(not_null<HistoryItem*> item) {
if (_history != item->history() || !_historyInited) { if (_history != item->history()
|| !_historyInited
|| item->isScheduled()) {
return; return;
} }
@ -2459,21 +2439,28 @@ void HistoryWidget::unreadMessageAdded(not_null<HistoryItem*> item) {
// - on second we get wrong doWeReadServerHistory() and read both. // - on second we get wrong doWeReadServerHistory() and read both.
session().data().sendHistoryChangeNotifications(); session().data().sendHistoryChangeNotifications();
const auto atBottom = (_scroll->scrollTop() >= _scroll->scrollTopMax()); if (item->isSending()) {
if (!atBottom) { synteticScrollToY(_scroll->scrollTopMax());
} else if (_scroll->scrollTop() < _scroll->scrollTopMax()) {
return; return;
} }
destroyUnreadBar(); if (item->showNotification()) {
if (!doWeReadServerHistory()) { destroyUnreadBar();
return; if (doWeReadServerHistory()) {
} if (item->isUnreadMention() && !item->isUnreadMedia()) {
if (item->isUnreadMention() && !item->isUnreadMedia()) { session().api().markMediaRead(item);
session().api().markMediaRead(item); }
} session().data().histories().readInboxOnNewMessage(item);
session().data().histories().readInboxOnNewMessage(item);
// Also clear possible scheduled messages notifications. // Also clear possible scheduled messages notifications.
Core::App().notifications().clearFromHistory(_history); Core::App().notifications().clearFromHistory(_history);
}
}
const auto view = item->mainView();
if (anim::Disabled() || !view) {
return;
}
_itemRevealPending.emplace(item);
} }
void HistoryWidget::unreadCountUpdated() { void HistoryWidget::unreadCountUpdated() {
@ -2904,9 +2891,13 @@ void HistoryWidget::delayedShowAt(MsgId showAtMsgId) {
} }
void HistoryWidget::handleScroll() { void HistoryWidget::handleScroll() {
preloadHistoryIfNeeded(); if (!_itemsRevealHeight) {
preloadHistoryIfNeeded();
}
visibleAreaUpdated(); visibleAreaUpdated();
updatePinnedViewer(); if (!_itemsRevealHeight) {
updatePinnedViewer();
}
if (!_synteticScrollEvent) { if (!_synteticScrollEvent) {
_lastUserScrolled = crl::now(); _lastUserScrolled = crl::now();
} }
@ -4692,6 +4683,11 @@ void HistoryWidget::itemRemoved(not_null<const HistoryItem*> item) {
updateControlsGeometry(); updateControlsGeometry();
} }
} }
const auto i = _itemRevealAnimations.find(item);
if (i != end(_itemRevealAnimations)) {
_itemRevealAnimations.erase(i);
revealItemsCallback();
}
} }
void HistoryWidget::itemEdited(not_null<HistoryItem*> item) { void HistoryWidget::itemEdited(not_null<HistoryItem*> item) {
@ -4784,6 +4780,9 @@ void HistoryWidget::updateHistoryGeometry(
bool initial, bool initial,
bool loadedDown, bool loadedDown,
const ScrollChange &change) { const ScrollChange &change) {
const auto guard = gsl::finally([&] {
_itemRevealPending.clear();
});
if (!_history || (initial && _historyInited) || (!initial && !_historyInited)) { if (!_history || (initial && _historyInited) || (!initial && !_historyInited)) {
return; return;
} }
@ -4867,20 +4866,74 @@ void HistoryWidget::updateHistoryGeometry(
newScrollTop += change.value; newScrollTop += change.value;
} else if (change.type == ScrollChangeNoJumpToBottom) { } else if (change.type == ScrollChangeNoJumpToBottom) {
newScrollTop = wasScrollTop; newScrollTop = wasScrollTop;
} else if (const auto add = base::take(_addToScroll)) {
newScrollTop += add;
} }
} }
const auto toY = std::clamp(newScrollTop, 0, _scroll->scrollTopMax()); const auto toY = std::clamp(newScrollTop, 0, _scroll->scrollTopMax());
synteticScrollToY(toY); 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() { void HistoryWidget::updateListSize() {
_list->recountHistoryGeometry(); _list->recountHistoryGeometry();
auto washidden = _scroll->isHidden(); auto washidden = _scroll->isHidden();
if (washidden) { if (washidden) {
_scroll->show(); _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(); _list->updateSize();
if (washidden) { if (washidden) {
_scroll->hide(); _scroll->hide();
@ -5029,6 +5082,30 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
update(); update();
} }
void HistoryWidget::botCallbackSent(not_null<HistoryItem*> 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 { int HistoryWidget::computeMaxFieldHeight() const {
const auto available = height() const auto available = height()
- _topBar->height() - _topBar->height()

View file

@ -225,6 +225,7 @@ public:
// With force=true the markup is updated even if it is // With force=true the markup is updated even if it is
// already shown for the passed history item. // already shown for the passed history item.
void updateBotKeyboard(History *h = nullptr, bool force = false); void updateBotKeyboard(History *h = nullptr, bool force = false);
void botCallbackSent(not_null<HistoryItem*> item);
void fastShowAtEnd(not_null<History*> history); void fastShowAtEnd(not_null<History*> history);
void applyDraft( void applyDraft(
@ -321,6 +322,11 @@ private:
Fn<void(MessageIdsList)> callback; Fn<void(MessageIdsList)> callback;
bool active = false; bool active = false;
}; };
struct ItemRevealAnimation {
int startHeight = 0;
int currentHeight = 0;
Ui::Animations::Simple animation;
};
enum class TextUpdateEvent { enum class TextUpdateEvent {
SaveDraft = (1 << 0), SaveDraft = (1 << 0),
SendTyping = (1 << 1), SendTyping = (1 << 1),
@ -414,7 +420,7 @@ private:
void historyDownAnimationFinish(); void historyDownAnimationFinish();
void unreadMentionsAnimationFinish(); void unreadMentionsAnimationFinish();
void sendButtonClicked(); void sendButtonClicked();
void unreadMessageAdded(not_null<HistoryItem*> item); void newItemAdded(not_null<HistoryItem*> item);
bool canSendFiles(not_null<const QMimeData*> data) const; bool canSendFiles(not_null<const QMimeData*> data) const;
bool confirmSendingFiles( bool confirmSendingFiles(
@ -530,6 +536,7 @@ private:
void updateHistoryGeometry(bool initial = false, bool loadedDown = false, const ScrollChange &change = { ScrollChangeNone, 0 }); void updateHistoryGeometry(bool initial = false, bool loadedDown = false, const ScrollChange &change = { ScrollChangeNone, 0 });
void updateListSize(); void updateListSize();
void revealItemsCallback();
// Does any of the shown histories has this flag set. // Does any of the shown histories has this flag set.
bool hasPendingResizedItems() const; bool hasPendingResizedItems() const;
@ -664,7 +671,6 @@ private:
bool _historyInited = false; bool _historyInited = false;
// If updateListSize() was called without updateHistoryGeometry(). // If updateListSize() was called without updateHistoryGeometry().
bool _updateHistoryGeometryRequired = false; bool _updateHistoryGeometryRequired = false;
int _addToScroll = 0;
int _lastScrollTop = 0; // gifs optimization int _lastScrollTop = 0; // gifs optimization
crl::time _lastScrolled = 0; crl::time _lastScrolled = 0;
@ -755,6 +761,12 @@ private:
base::weak_ptr<Ui::Toast::Instance> _topToast; base::weak_ptr<Ui::Toast::Instance> _topToast;
std::unique_ptr<ChooseMessagesForReport> _chooseForReport; std::unique_ptr<ChooseMessagesForReport> _chooseForReport;
base::flat_set<not_null<HistoryItem*>> _itemRevealPending;
base::flat_map<
not_null<HistoryItem*>,
ItemRevealAnimation> _itemRevealAnimations;
int _itemsRevealHeight = 0;
object_ptr<Ui::PlainShadow> _topShadow; object_ptr<Ui::PlainShadow> _topShadow;
bool _inGrab = false; bool _inGrab = false;

View file

@ -1124,7 +1124,7 @@ bool ListWidget::loadedAtBottom() const {
} }
bool ListWidget::isEmpty() const { bool ListWidget::isEmpty() const {
return loadedAtTop() && loadedAtBottom() && (_itemsHeight == 0); return loadedAtTop() && loadedAtBottom();
} }
int ListWidget::itemMinimalHeight() const { int ListWidget::itemMinimalHeight() const {