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();
}
void Session::notifyUnreadItemAdded(not_null<HistoryItem*> item) {
_unreadItemAdded.fire_copy(item);
void Session::notifyNewItemAdded(not_null<HistoryItem*> item) {
_newItemAdded.fire_copy(item);
}
rpl::producer<not_null<HistoryItem*>> Session::unreadItemAdded() const {
return _unreadItemAdded.events();
rpl::producer<not_null<HistoryItem*>> Session::newItemAdded() const {
return _newItemAdded.events();
}
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;
void notifyViewLayoutChange(not_null<const ViewElement*> view);
[[nodiscard]] rpl::producer<not_null<const ViewElement*>> viewLayoutChanged() const;
void notifyUnreadItemAdded(not_null<HistoryItem*> item);
[[nodiscard]] rpl::producer<not_null<HistoryItem*>> unreadItemAdded() const;
void notifyNewItemAdded(not_null<HistoryItem*> item);
[[nodiscard]] rpl::producer<not_null<HistoryItem*>> newItemAdded() const;
void requestItemRepaint(not_null<const HistoryItem*> item);
[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemRepaintRequest() const;
void requestViewRepaint(not_null<const ViewElement*> view);
@ -836,7 +836,7 @@ private:
rpl::event_stream<IdChange> _itemIdChanges;
rpl::event_stream<not_null<const HistoryItem*>> _itemLayoutChanges;
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 ViewElement*>> _viewRepaintRequest;
rpl::event_stream<not_null<const HistoryItem*>> _itemResizeRequest;

View file

@ -1072,16 +1072,16 @@ void History::newItemAdded(not_null<HistoryItem*> 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()) {

View file

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

View file

@ -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;

View file

@ -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<HistoryItem*> item) {
unreadMessageAdded(item);
}, lifetime());
session().data().itemRemoved(
) | rpl::start_with_next([=](not_null<const HistoryItem*> 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<HistoryItem*> item) {
if (_history != item->history() || !_historyInited) {
void HistoryWidget::newItemAdded(not_null<HistoryItem*> item) {
if (_history != item->history()
|| !_historyInited
|| item->isScheduled()) {
return;
}
@ -2459,21 +2439,28 @@ void HistoryWidget::unreadMessageAdded(not_null<HistoryItem*> 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<const HistoryItem*> item) {
updateControlsGeometry();
}
}
const auto i = _itemRevealAnimations.find(item);
if (i != end(_itemRevealAnimations)) {
_itemRevealAnimations.erase(i);
revealItemsCallback();
}
}
void HistoryWidget::itemEdited(not_null<HistoryItem*> 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<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 {
const auto available = height()
- _topBar->height()

View file

@ -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<HistoryItem*> item);
void fastShowAtEnd(not_null<History*> history);
void applyDraft(
@ -321,6 +322,11 @@ private:
Fn<void(MessageIdsList)> 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<HistoryItem*> item);
void newItemAdded(not_null<HistoryItem*> item);
bool canSendFiles(not_null<const QMimeData*> 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<Ui::Toast::Instance> _topToast;
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;
bool _inGrab = false;

View file

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