mirror of https://github.com/procxx/kepka.git
Add an animated scroll to current media message.
This commit is contained in:
parent
6bde8cdce4
commit
0bfff65306
|
@ -59,6 +59,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "storage/file_upload.h"
|
#include "storage/file_upload.h"
|
||||||
#include "media/media_audio.h"
|
#include "media/media_audio.h"
|
||||||
#include "media/media_audio_capture.h"
|
#include "media/media_audio_capture.h"
|
||||||
|
#include "media/player/media_player_instance.h"
|
||||||
#include "storage/localstorage.h"
|
#include "storage/localstorage.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "window/top_bar_widget.h"
|
#include "window/top_bar_widget.h"
|
||||||
|
@ -81,6 +82,8 @@ constexpr auto kMessagesPerPage = 50;
|
||||||
constexpr auto kPreloadHeightsCount = 3; // when 3 screens to scroll left make a preload request
|
constexpr auto kPreloadHeightsCount = 3; // when 3 screens to scroll left make a preload request
|
||||||
constexpr auto kTabbedSelectorToggleTooltipTimeoutMs = 3000;
|
constexpr auto kTabbedSelectorToggleTooltipTimeoutMs = 3000;
|
||||||
constexpr auto kTabbedSelectorToggleTooltipCount = 3;
|
constexpr auto kTabbedSelectorToggleTooltipCount = 3;
|
||||||
|
constexpr auto kScrollToVoiceAfterScrolledMs = 1000;
|
||||||
|
constexpr auto kSkipRepaintWhileScrollMs = 100;
|
||||||
|
|
||||||
ApiWrap::RequestMessageDataCallback replyEditMessageDataCallback() {
|
ApiWrap::RequestMessageDataCallback replyEditMessageDataCallback() {
|
||||||
return [](ChannelData *channel, MsgId msgId) {
|
return [](ChannelData *channel, MsgId msgId) {
|
||||||
|
@ -633,10 +636,68 @@ HistoryWidget::HistoryWidget(QWidget *parent, gsl::not_null<Window::Controller*>
|
||||||
updateControlsVisibility();
|
updateControlsVisibility();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
subscribe(Media::Player::instance()->switchToNextNotifier(), [this](const Media::Player::Instance::Switch &pair) {
|
||||||
|
if (pair.from.type() == AudioMsgId::Type::Voice) {
|
||||||
|
scrollToCurrentVoiceMessage(pair.from.contextId(), pair.to);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
orderWidgets();
|
orderWidgets();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HistoryWidget::scrollToCurrentVoiceMessage(FullMsgId fromId, FullMsgId toId) {
|
||||||
|
if (getms() <= _lastUserScrolled + kScrollToVoiceAfterScrolledMs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!_list) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto from = App::histItemById(fromId);
|
||||||
|
auto to = App::histItemById(toId);
|
||||||
|
if (!from || !to) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If history has pending resize items, the scrollTopItem won't be updated.
|
||||||
|
// And the scrollTop will be reset back to scrollTopItem + scrollTopOffset.
|
||||||
|
notify_handlePendingHistoryUpdate();
|
||||||
|
|
||||||
|
auto fromTop = _list->itemTop(from);
|
||||||
|
auto toTop = _list->itemTop(to);
|
||||||
|
if (fromTop < 0 || toTop < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto scrollTop = _scroll->scrollTop();
|
||||||
|
auto scrollBottom = scrollTop + _scroll->height();
|
||||||
|
auto fromBottom = fromTop + from->height();
|
||||||
|
if (fromTop < scrollBottom && fromBottom > scrollTop) {
|
||||||
|
auto toBottom = toTop + to->height();
|
||||||
|
if ((toTop < scrollTop && toBottom < scrollBottom) || (toTop > scrollTop && toBottom > scrollBottom)) {
|
||||||
|
auto scrollTo = snap(itemTopForHighlight(to), 0, _scroll->scrollTopMax());
|
||||||
|
_scrollToMediaMessageAnimation.finish();
|
||||||
|
_scrollToMediaMessageAnimation.start([this, toId] {
|
||||||
|
auto toTop = _list->itemTop(App::histItemById(toId));
|
||||||
|
if (toTop < 0) {
|
||||||
|
_scrollToMediaMessageAnimation.finish();
|
||||||
|
} else {
|
||||||
|
synteticScrollToY(qRound(_scrollToMediaMessageAnimation.current()) + toTop);
|
||||||
|
}
|
||||||
|
}, scrollTop - toTop, scrollTo - toTop, 200, anim::sineInOut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int HistoryWidget::itemTopForHighlight(HistoryItem *item) const {
|
||||||
|
auto itemTop = _list->itemTop(item);
|
||||||
|
auto heightLeft = (_scroll->height() - item->height());
|
||||||
|
if (heightLeft <= 0) {
|
||||||
|
return itemTop;
|
||||||
|
}
|
||||||
|
return qMax(itemTop - (heightLeft / 2), 0);
|
||||||
|
}
|
||||||
|
|
||||||
void HistoryWidget::start() {
|
void HistoryWidget::start() {
|
||||||
connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated()));
|
connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated()));
|
||||||
updateRecentStickers();
|
updateRecentStickers();
|
||||||
|
@ -2235,7 +2296,7 @@ void HistoryWidget::historyToDown(History *history) {
|
||||||
migrated->forgetScrollState();
|
migrated->forgetScrollState();
|
||||||
}
|
}
|
||||||
if (history == _history) {
|
if (history == _history) {
|
||||||
_scroll->scrollToY(_scroll->scrollTopMax());
|
synteticScrollToY(_scroll->scrollTopMax());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2571,6 +2632,9 @@ void HistoryWidget::onScroll() {
|
||||||
App::checkImageCacheSize();
|
App::checkImageCacheSize();
|
||||||
preloadHistoryIfNeeded();
|
preloadHistoryIfNeeded();
|
||||||
visibleAreaUpdated();
|
visibleAreaUpdated();
|
||||||
|
if (!_synteticScrollEvent) {
|
||||||
|
_lastUserScrolled = getms();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::visibleAreaUpdated() {
|
void HistoryWidget::visibleAreaUpdated() {
|
||||||
|
@ -2613,9 +2677,9 @@ void HistoryWidget::preloadHistoryIfNeeded() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (st != _lastScroll) {
|
if (st != _lastScrollTop) {
|
||||||
_lastScrolled = getms();
|
_lastScrolled = getms();
|
||||||
_lastScroll = st;
|
_lastScrollTop = st;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4531,10 +4595,10 @@ bool HistoryWidget::isItemVisible(HistoryItem *item) {
|
||||||
void HistoryWidget::ui_repaintHistoryItem(const HistoryItem *item) {
|
void HistoryWidget::ui_repaintHistoryItem(const HistoryItem *item) {
|
||||||
if (_peer && _list && (item->history() == _history || (_migrated && item->history() == _migrated))) {
|
if (_peer && _list && (item->history() == _history || (_migrated && item->history() == _migrated))) {
|
||||||
auto ms = getms();
|
auto ms = getms();
|
||||||
if (_lastScrolled + 100 <= ms) {
|
if (_lastScrolled + kSkipRepaintWhileScrollMs <= ms) {
|
||||||
_list->repaintItem(item);
|
_list->repaintItem(item);
|
||||||
} else {
|
} else {
|
||||||
_updateHistoryItems.start(_lastScrolled + 100 - ms);
|
_updateHistoryItems.start(_lastScrolled + kSkipRepaintWhileScrollMs - ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4543,10 +4607,10 @@ void HistoryWidget::onUpdateHistoryItems() {
|
||||||
if (!_list) return;
|
if (!_list) return;
|
||||||
|
|
||||||
auto ms = getms();
|
auto ms = getms();
|
||||||
if (_lastScrolled + 100 <= ms) {
|
if (_lastScrolled + kSkipRepaintWhileScrollMs <= ms) {
|
||||||
_list->update();
|
_list->update();
|
||||||
} else {
|
} else {
|
||||||
_updateHistoryItems.start(_lastScrolled + 100 - ms);
|
_updateHistoryItems.start(_lastScrolled + kSkipRepaintWhileScrollMs - ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4732,7 +4796,7 @@ void HistoryWidget::updateListSize(bool initial, bool loadedDown, const ScrollCh
|
||||||
if (_scroll->scrollTop() == toY) {
|
if (_scroll->scrollTop() == toY) {
|
||||||
visibleAreaUpdated();
|
visibleAreaUpdated();
|
||||||
} else {
|
} else {
|
||||||
_scroll->scrollToY(toY);
|
synteticScrollToY(toY);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -4745,27 +4809,27 @@ void HistoryWidget::updateListSize(bool initial, bool loadedDown, const ScrollCh
|
||||||
if (initial && (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem))) {
|
if (initial && (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem))) {
|
||||||
toY = _list->historyScrollTop();
|
toY = _list->historyScrollTop();
|
||||||
} else if (initial && _migrated && _showAtMsgId < 0 && -_showAtMsgId < ServerMaxMsgId) {
|
} else if (initial && _migrated && _showAtMsgId < 0 && -_showAtMsgId < ServerMaxMsgId) {
|
||||||
HistoryItem *item = App::histItemById(0, -_showAtMsgId);
|
auto item = App::histItemById(0, -_showAtMsgId);
|
||||||
int32 iy = _list->itemTop(item);
|
auto iy = _list->itemTop(item);
|
||||||
if (iy < 0) {
|
if (iy < 0) {
|
||||||
setMsgId(0);
|
setMsgId(0);
|
||||||
_histInited = false;
|
_histInited = false;
|
||||||
return updateListSize(initial, false, change);
|
return updateListSize(initial, false, change);
|
||||||
} else {
|
} else {
|
||||||
toY = (_scroll->height() > item->height()) ? qMax(iy - (_scroll->height() - item->height()) / 2, 0) : iy;
|
toY = itemTopForHighlight(item);
|
||||||
_animActiveStart = getms();
|
_animActiveStart = getms();
|
||||||
_animActiveTimer.start(AnimationTimerDelta);
|
_animActiveTimer.start(AnimationTimerDelta);
|
||||||
_activeAnimMsgId = _showAtMsgId;
|
_activeAnimMsgId = _showAtMsgId;
|
||||||
}
|
}
|
||||||
} else if (initial && _showAtMsgId > 0) {
|
} else if (initial && _showAtMsgId > 0) {
|
||||||
HistoryItem *item = App::histItemById(_channel, _showAtMsgId);
|
auto item = App::histItemById(_channel, _showAtMsgId);
|
||||||
int32 iy = _list->itemTop(item);
|
auto iy = _list->itemTop(item);
|
||||||
if (iy < 0) {
|
if (iy < 0) {
|
||||||
setMsgId(0);
|
setMsgId(0);
|
||||||
_histInited = false;
|
_histInited = false;
|
||||||
return updateListSize(initial, false, change);
|
return updateListSize(initial, false, change);
|
||||||
} else {
|
} else {
|
||||||
toY = (_scroll->height() > item->height()) ? qMax(iy - (_scroll->height() - item->height()) / 2, 0) : iy;
|
toY = itemTopForHighlight(item);
|
||||||
_animActiveStart = getms();
|
_animActiveStart = getms();
|
||||||
_animActiveTimer.start(AnimationTimerDelta);
|
_animActiveTimer.start(AnimationTimerDelta);
|
||||||
_activeAnimMsgId = _showAtMsgId;
|
_activeAnimMsgId = _showAtMsgId;
|
||||||
|
@ -4806,7 +4870,7 @@ void HistoryWidget::updateListSize(bool initial, bool loadedDown, const ScrollCh
|
||||||
if (_scroll->scrollTop() == toY) {
|
if (_scroll->scrollTop() == toY) {
|
||||||
visibleAreaUpdated();
|
visibleAreaUpdated();
|
||||||
} else {
|
} else {
|
||||||
_scroll->scrollToY(toY);
|
synteticScrollToY(toY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5187,7 +5251,7 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() {
|
||||||
result = true;
|
result = true;
|
||||||
|
|
||||||
if (_scroll->scrollTop() != unreadBarTop()) {
|
if (_scroll->scrollTop() != unreadBarTop()) {
|
||||||
_scroll->scrollToY(_scroll->scrollTop() + st::historyReplyHeight);
|
synteticScrollToY(_scroll->scrollTop() + st::historyReplyHeight);
|
||||||
}
|
}
|
||||||
} else if (_pinnedBar->msgId != pinnedMsgId) {
|
} else if (_pinnedBar->msgId != pinnedMsgId) {
|
||||||
_pinnedBar->msgId = pinnedMsgId;
|
_pinnedBar->msgId = pinnedMsgId;
|
||||||
|
@ -5202,7 +5266,7 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() {
|
||||||
destroyPinnedBar();
|
destroyPinnedBar();
|
||||||
result = true;
|
result = true;
|
||||||
if (_scroll->scrollTop() != unreadBarTop()) {
|
if (_scroll->scrollTop() != unreadBarTop()) {
|
||||||
_scroll->scrollToY(_scroll->scrollTop() - st::historyReplyHeight);
|
synteticScrollToY(_scroll->scrollTop() - st::historyReplyHeight);
|
||||||
}
|
}
|
||||||
updateControlsGeometry();
|
updateControlsGeometry();
|
||||||
}
|
}
|
||||||
|
@ -6390,7 +6454,7 @@ QPoint HistoryWidget::clampMousePosition(QPoint point) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::onScrollTimer() {
|
void HistoryWidget::onScrollTimer() {
|
||||||
int32 d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed));
|
auto d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed));
|
||||||
_scroll->scrollToY(_scroll->scrollTop() + d);
|
_scroll->scrollToY(_scroll->scrollTop() + d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6421,4 +6485,10 @@ bool HistoryWidget::touchScroll(const QPoint &delta) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HistoryWidget::synteticScrollToY(int y) {
|
||||||
|
_synteticScrollEvent = true;
|
||||||
|
_scroll->scrollToY(y);
|
||||||
|
_synteticScrollEvent = false;
|
||||||
|
}
|
||||||
|
|
||||||
HistoryWidget::~HistoryWidget() = default;
|
HistoryWidget::~HistoryWidget() = default;
|
||||||
|
|
|
@ -643,6 +643,13 @@ private:
|
||||||
// Counts scrollTop for placing the scroll right at the unread
|
// Counts scrollTop for placing the scroll right at the unread
|
||||||
// messages bar, choosing from _history and _migrated unreadBar.
|
// messages bar, choosing from _history and _migrated unreadBar.
|
||||||
int unreadBarTop() const;
|
int unreadBarTop() const;
|
||||||
|
int itemTopForHighlight(HistoryItem *item) const;
|
||||||
|
void scrollToCurrentVoiceMessage(FullMsgId fromId, FullMsgId toId);
|
||||||
|
|
||||||
|
// Scroll to current y without updating the _lastUserScrolled time.
|
||||||
|
// Used to distinguish between user scrolls and syntetic scrolls.
|
||||||
|
// This one is syntetic.
|
||||||
|
void synteticScrollToY(int y);
|
||||||
|
|
||||||
void saveGifDone(DocumentData *doc, const MTPBool &result);
|
void saveGifDone(DocumentData *doc, const MTPBool &result);
|
||||||
|
|
||||||
|
@ -721,10 +728,14 @@ private:
|
||||||
bool _histInited = false; // initial updateListSize() called
|
bool _histInited = false; // initial updateListSize() called
|
||||||
int _addToScroll = 0;
|
int _addToScroll = 0;
|
||||||
|
|
||||||
int _lastScroll = 0;// gifs optimization
|
int _lastScrollTop = 0; // gifs optimization
|
||||||
TimeMs _lastScrolled = 0;
|
TimeMs _lastScrolled = 0;
|
||||||
QTimer _updateHistoryItems;
|
QTimer _updateHistoryItems;
|
||||||
|
|
||||||
|
TimeMs _lastUserScrolled = 0;
|
||||||
|
bool _synteticScrollEvent = false;
|
||||||
|
Animation _scrollToMediaMessageAnimation;
|
||||||
|
|
||||||
Animation _historyDownShown;
|
Animation _historyDownShown;
|
||||||
bool _historyDownIsShown = false;
|
bool _historyDownIsShown = false;
|
||||||
object_ptr<Ui::HistoryDownButton> _historyDown;
|
object_ptr<Ui::HistoryDownButton> _historyDown;
|
||||||
|
|
|
@ -166,7 +166,7 @@ void Instance::rebuildPlaylist(Data *data) {
|
||||||
_playlistChangedNotifier.notify(data->type, true);
|
_playlistChangedNotifier.notify(data->type, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Instance::moveInPlaylist(Data *data, int delta) {
|
bool Instance::moveInPlaylist(Data *data, int delta, bool autonext) {
|
||||||
Expects(data != nullptr);
|
Expects(data != nullptr);
|
||||||
|
|
||||||
auto index = data->playlist.indexOf(data->current.contextId());
|
auto index = data->playlist.indexOf(data->current.contextId());
|
||||||
|
@ -179,6 +179,9 @@ bool Instance::moveInPlaylist(Data *data, int delta) {
|
||||||
auto msgId = data->playlist[newIndex];
|
auto msgId = data->playlist[newIndex];
|
||||||
if (auto item = App::histItemById(msgId)) {
|
if (auto item = App::histItemById(msgId)) {
|
||||||
if (auto media = item->getMedia()) {
|
if (auto media = item->getMedia()) {
|
||||||
|
if (autonext) {
|
||||||
|
_switchToNextNotifier.notify({ data->current, msgId });
|
||||||
|
}
|
||||||
media->playInline();
|
media->playInline();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -258,14 +261,14 @@ void Instance::playPause(AudioMsgId::Type type) {
|
||||||
|
|
||||||
bool Instance::next(AudioMsgId::Type type) {
|
bool Instance::next(AudioMsgId::Type type) {
|
||||||
if (auto data = getData(type)) {
|
if (auto data = getData(type)) {
|
||||||
return moveInPlaylist(data, 1);
|
return moveInPlaylist(data, 1, false);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Instance::previous(AudioMsgId::Type type) {
|
bool Instance::previous(AudioMsgId::Type type) {
|
||||||
if (auto data = getData(type)) {
|
if (auto data = getData(type)) {
|
||||||
return moveInPlaylist(data, -1);
|
return moveInPlaylist(data, -1, false);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -323,7 +326,7 @@ void Instance::emitUpdate(AudioMsgId::Type type, CheckCallback check) {
|
||||||
if (data->isPlaying && state.state == State::StoppedAtEnd) {
|
if (data->isPlaying && state.state == State::StoppedAtEnd) {
|
||||||
if (data->repeatEnabled) {
|
if (data->repeatEnabled) {
|
||||||
play(data->current);
|
play(data->current);
|
||||||
} else if (!next(type)) {
|
} else if (!moveInPlaylist(data, 1, true)) {
|
||||||
_tracksFinishedNotifier.notify(type);
|
_tracksFinishedNotifier.notify(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,14 @@ public:
|
||||||
return QList<FullMsgId>();
|
return QList<FullMsgId>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Switch {
|
||||||
|
AudioMsgId from;
|
||||||
|
FullMsgId to;
|
||||||
|
};
|
||||||
|
|
||||||
|
base::Observable<Switch> &switchToNextNotifier() {
|
||||||
|
return _switchToNextNotifier;
|
||||||
|
}
|
||||||
base::Observable<bool> &usePanelPlayer() {
|
base::Observable<bool> &usePanelPlayer() {
|
||||||
return _usePanelPlayer;
|
return _usePanelPlayer;
|
||||||
}
|
}
|
||||||
|
@ -160,7 +168,7 @@ private:
|
||||||
void checkPeerUpdate(AudioMsgId::Type type, const Notify::PeerUpdate &update);
|
void checkPeerUpdate(AudioMsgId::Type type, const Notify::PeerUpdate &update);
|
||||||
void setCurrent(const AudioMsgId &audioId);
|
void setCurrent(const AudioMsgId &audioId);
|
||||||
void rebuildPlaylist(Data *data);
|
void rebuildPlaylist(Data *data);
|
||||||
bool moveInPlaylist(Data *data, int delta);
|
bool moveInPlaylist(Data *data, int delta, bool autonext);
|
||||||
void preloadNext(Data *data);
|
void preloadNext(Data *data);
|
||||||
void handleLogout();
|
void handleLogout();
|
||||||
|
|
||||||
|
@ -188,6 +196,7 @@ private:
|
||||||
Data _songData;
|
Data _songData;
|
||||||
Data _voiceData;
|
Data _voiceData;
|
||||||
|
|
||||||
|
base::Observable<Switch> _switchToNextNotifier;
|
||||||
base::Observable<bool> _usePanelPlayer;
|
base::Observable<bool> _usePanelPlayer;
|
||||||
base::Observable<bool> _titleButtonOver;
|
base::Observable<bool> _titleButtonOver;
|
||||||
base::Observable<bool> _playerWidgetOver;
|
base::Observable<bool> _playerWidgetOver;
|
||||||
|
|
Loading…
Reference in New Issue