From e992702783286cf7ed797fbe9efbef1122c1af20 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 9 Nov 2018 17:54:34 +0400 Subject: [PATCH] Load chats in chunks in support mode. --- Telegram/SourceFiles/auth_session.cpp | 18 ++ Telegram/SourceFiles/auth_session.h | 8 + Telegram/SourceFiles/dialogs/dialogs.style | 20 ++- .../SourceFiles/dialogs/dialogs_widget.cpp | 163 +++++++++++++++--- Telegram/SourceFiles/dialogs/dialogs_widget.h | 12 +- .../SourceFiles/settings/settings_chat.cpp | 85 +++++++-- 6 files changed, 263 insertions(+), 43 deletions(-) diff --git a/Telegram/SourceFiles/auth_session.cpp b/Telegram/SourceFiles/auth_session.cpp index 1a18fb5f6..1eb8bb80a 100644 --- a/Telegram/SourceFiles/auth_session.cpp +++ b/Telegram/SourceFiles/auth_session.cpp @@ -83,6 +83,7 @@ QByteArray AuthSessionSettings::serialize() const { stream << qint32(_variables.supportSwitch); stream << qint32(_variables.supportFixChatsOrder ? 1 : 0); stream << qint32(_variables.supportTemplatesAutocomplete ? 1 : 0); + stream << qint32(_variables.supportChatsTimeSlice.current()); } return result; } @@ -113,6 +114,7 @@ void AuthSessionSettings::constructFromSerialized(const QByteArray &serialized) qint32 supportSwitch = static_cast(_variables.supportSwitch); qint32 supportFixChatsOrder = _variables.supportFixChatsOrder ? 1 : 0; qint32 supportTemplatesAutocomplete = _variables.supportTemplatesAutocomplete ? 1 : 0; + qint32 supportChatsTimeSlice = _variables.supportChatsTimeSlice.current(); stream >> selectorTab; stream >> lastSeenWarningSeen; @@ -176,6 +178,9 @@ void AuthSessionSettings::constructFromSerialized(const QByteArray &serialized) if (!stream.atEnd()) { stream >> supportTemplatesAutocomplete; } + if (!stream.atEnd()) { + stream >> supportChatsTimeSlice; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for AuthSessionSettings::constructFromSerialized()")); @@ -243,6 +248,19 @@ void AuthSessionSettings::constructFromSerialized(const QByteArray &serialized) } _variables.supportFixChatsOrder = (supportFixChatsOrder == 1); _variables.supportTemplatesAutocomplete = (supportTemplatesAutocomplete == 1); + _variables.supportChatsTimeSlice = supportChatsTimeSlice; +} + +void AuthSessionSettings::setSupportChatsTimeSlice(int slice) { + _variables.supportChatsTimeSlice = slice; +} + +int AuthSessionSettings::supportChatsTimeSlice() const { + return _variables.supportChatsTimeSlice.current(); +} + +rpl::producer AuthSessionSettings::supportChatsTimeSliceValue() const { + return _variables.supportChatsTimeSlice.value(); } void AuthSessionSettings::setTabbedSelectorSectionEnabled(bool enabled) { diff --git a/Telegram/SourceFiles/auth_session.h b/Telegram/SourceFiles/auth_session.h index 8775453bf..a63bed5db 100644 --- a/Telegram/SourceFiles/auth_session.h +++ b/Telegram/SourceFiles/auth_session.h @@ -102,6 +102,9 @@ public: bool supportTemplatesAutocomplete() const { return _variables.supportTemplatesAutocomplete; } + void setSupportChatsTimeSlice(int slice); + int supportChatsTimeSlice() const; + rpl::producer supportChatsTimeSliceValue() const; ChatHelpers::SelectorTab selectorTab() const { return _variables.selectorTab; @@ -220,9 +223,14 @@ private: = Calls::PeerToPeer(); Ui::InputSubmitSettings sendSubmitWay; + static constexpr auto kDefaultSupportChatsLimitSlice + = 30 * 24 * 60 * 60; + Support::SwitchSettings supportSwitch; bool supportFixChatsOrder = true; bool supportTemplatesAutocomplete = true; + rpl::variable supportChatsTimeSlice + = kDefaultSupportChatsLimitSlice; }; rpl::event_stream _thirdSectionInfoEnabledValue; diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 1922f5cad..4071006da 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -194,10 +194,28 @@ dialogsUpdateButton: FlatButton { color: activeButtonBgRipple; } } - dialogsInstallUpdate: icon {{ "install_update", activeButtonFg }}; dialogsInstallUpdateOver: icon {{ "install_update", activeButtonFgOver }}; +dialogsLoadMoreButton: FlatButton(dialogsUpdateButton) { + color: lightButtonFg; + overColor: lightButtonFg; + bgColor: lightButtonBg; + overBgColor: lightButtonBgOver; + ripple: RippleAnimation(defaultRippleAnimation) { + color: lightButtonBgRipple; + } + + height: 36px; + textTop: 9px; +} +dialogsLoadMore: icon {{ "install_update-flip_vertical", lightButtonFg }}; +dialogsLoadMoreLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { + color: lightButtonFg; + thickness: 3px; + size: size(12px, 12px); +} + dialogsForwardHeight: 32px; dialogsForwardTextLeft: 35px; dialogsForwardTextTop: 6px; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index f1b460c05..75793db82 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/wrap/fade_wrap.h" +#include "ui/effects/radial_animation.h" #include "lang/lang_keys.h" #include "application.h" #include "mainwindow.h" @@ -43,9 +44,16 @@ QString SwitchToChooseFromQuery() { } // namespace -class DialogsWidget::UpdateButton : public Ui::RippleButton { +class DialogsWidget::BottomButton : public Ui::RippleButton { public: - UpdateButton(QWidget *parent); + BottomButton( + QWidget *parent, + const QString &text, + const style::FlatButton &st, + const style::icon &icon, + const style::icon &iconOver); + + void setText(const QString &text); protected: void paintEvent(QPaintEvent *e) override; @@ -53,39 +61,84 @@ protected: void onStateChanged(State was, StateChangeSource source) override; private: + void step_radial(TimeMs ms, bool timer); + QString _text; const style::FlatButton &_st; + const style::icon &_icon; + const style::icon &_iconOver; + std::unique_ptr _loading; }; -DialogsWidget::UpdateButton::UpdateButton(QWidget *parent) : RippleButton(parent, st::dialogsUpdateButton.ripple) -, _text(lang(lng_update_telegram).toUpper()) -, _st(st::dialogsUpdateButton) { +DialogsWidget::BottomButton::BottomButton( + QWidget *parent, + const QString &text, + const style::FlatButton &st, + const style::icon &icon, + const style::icon &iconOver) +: RippleButton(parent, st.ripple) +, _text(text.toUpper()) +, _st(st) +, _icon(icon) +, _iconOver(iconOver) { resize(st::columnMinimalWidthLeft, _st.height); } -void DialogsWidget::UpdateButton::onStateChanged(State was, StateChangeSource source) { - RippleButton::onStateChanged(was, source); +void DialogsWidget::BottomButton::setText(const QString &text) { + _text = text.toUpper(); update(); } -void DialogsWidget::UpdateButton::paintEvent(QPaintEvent *e) { - QPainter p(this); +void DialogsWidget::BottomButton::step_radial(TimeMs ms, bool timer) { + if (timer && !anim::Disabled() && width() < st::columnMinimalWidthLeft) { + update(); + } +} + +void DialogsWidget::BottomButton::onStateChanged(State was, StateChangeSource source) { + RippleButton::onStateChanged(was, source); + if ((was & StateFlag::Disabled) != (state() & StateFlag::Disabled)) { + _loading = isDisabled() + ? std::make_unique( + animation(this, &BottomButton::step_radial), + st::dialogsLoadMoreLoading) + : nullptr; + if (_loading) { + _loading->start(); + } + } + update(); +} + +void DialogsWidget::BottomButton::paintEvent(QPaintEvent *e) { + Painter p(this); + + const auto over = isOver() && !isDisabled(); QRect r(0, height() - _st.height, width(), _st.height); - p.fillRect(r, isOver() ? _st.overBgColor : _st.bgColor); + p.fillRect(r, over ? _st.overBgColor : _st.bgColor); - paintRipple(p, 0, 0, getms()); + if (!isDisabled()) { + paintRipple(p, 0, 0, getms()); + } - p.setFont(isOver() ? _st.overFont : _st.font); + p.setFont(over ? _st.overFont : _st.font); p.setRenderHint(QPainter::TextAntialiasing); - p.setPen(isOver() ? _st.overColor : _st.color); + p.setPen(over ? _st.overColor : _st.color); if (width() >= st::columnMinimalWidthLeft) { r.setTop(_st.textTop); p.drawText(r, _text, style::al_top); + } else if (isDisabled() && _loading) { + _loading->draw( + p, + QPoint( + (width() - st::dialogsLoadMoreLoading.size.width()) / 2, + (height() - st::dialogsLoadMoreLoading.size.height()) / 2), + width()); } else { - (isOver() ? st::dialogsInstallUpdateOver : st::dialogsInstallUpdate).paintInCenter(p, r); + (over ? _iconOver : _icon).paintInCenter(p, r); } } @@ -173,6 +226,7 @@ DialogsWidget::DialogsWidget(QWidget *parent, not_null cont updateJumpToDateVisibility(true); updateSearchFromVisibility(true); setupConnectingWidget(); + setupSupportLoadingLimit(); } void DialogsWidget::setupConnectingWidget() { @@ -181,13 +235,29 @@ void DialogsWidget::setupConnectingWidget() { Window::AdaptiveIsOneColumn()); } +void DialogsWidget::setupSupportLoadingLimit() { + if (!Auth().supportMode()) { + return; + } + Auth().settings().supportChatsTimeSliceValue( + ) | rpl::start_with_next([=](int seconds) { + _dialogsLoadTill = seconds ? std::max(unixtime() - seconds, 0) : 0; + refreshLoadMoreButton(); + }, lifetime()); +} + void DialogsWidget::checkUpdateStatus() { Expects(!Core::UpdaterDisabled()); using Checker = Core::UpdateChecker; if (Checker().state() == Checker::State::Ready) { if (_updateTelegram) return; - _updateTelegram.create(this); + _updateTelegram.create( + this, + lang(lng_update_telegram), + st::dialogsUpdateButton, + st::dialogsInstallUpdate, + st::dialogsInstallUpdateOver); _updateTelegram->show(); _updateTelegram->setClickedCallback([] { Core::checkReadyUpdate(); @@ -369,6 +439,10 @@ void DialogsWidget::dialogsReceived( _dialogsRequestId = 0; loadDialogs(); + if (!_dialogsRequestId) { + refreshLoadMoreButton(); + } + Auth().data().moreChatsLoaded().notify(); if (_dialogsFull && _pinnedDialogsReceived) { Auth().data().allChatsLoaded().set(true); @@ -425,6 +499,36 @@ void DialogsWidget::updateDialogsOffset( } } +void DialogsWidget::refreshLoadMoreButton() { + if (_dialogsFull || !_dialogsLoadTill) { + _loadMoreChats.destroy(); + updateControlsGeometry(); + return; + } + if (!_loadMoreChats) { + _loadMoreChats.create( + this, + "Load more", + st::dialogsLoadMoreButton, + st::dialogsLoadMore, + st::dialogsLoadMore); + _loadMoreChats->addClickHandler([=] { + if (_loadMoreChats->isDisabled()) { + return; + } + const auto max = Auth().settings().supportChatsTimeSlice(); + _dialogsLoadTill = _dialogsOffsetDate + ? (_dialogsOffsetDate - max) + : (unixtime() - max); + loadDialogs(); + }); + updateControlsGeometry(); + } + const auto loading = !loadingBlockedByDate(); + _loadMoreChats->setDisabled(loading); + _loadMoreChats->setText(loading ? "Loading..." : "Load more"); +} + void DialogsWidget::pinnedDialogsReceived( const MTPmessages_PeerDialogs &result, mtpRequestId requestId) { @@ -731,11 +835,21 @@ void DialogsWidget::onSearchMore() { } } +bool DialogsWidget::loadingBlockedByDate() const { + return !_dialogsFull + && !_dialogsRequestId + && (_dialogsLoadTill > 0) + && (_dialogsOffsetDate > 0) + && (_dialogsOffsetDate <= _dialogsLoadTill); +} + void DialogsWidget::loadDialogs() { if (_dialogsRequestId) return; if (_dialogsFull) { _inner->addAllSavedPeers(); return; + } else if (loadingBlockedByDate()) { + return; } const auto firstLoad = !_dialogsOffsetDate; @@ -759,6 +873,7 @@ void DialogsWidget::loadDialogs() { if (!_pinnedDialogsReceived) { loadPinnedDialogs(); } + refreshLoadMoreButton(); } void DialogsWidget::loadPinnedDialogs() { @@ -1205,11 +1320,19 @@ void DialogsWidget::updateControlsGeometry() { auto addToScroll = App::main() ? App::main()->contentScrollAddToY() : 0; auto newScrollTop = _scroll->scrollTop() + addToScroll; auto scrollHeight = height() - scrollTop; - if (_updateTelegram) { - auto updateHeight = _updateTelegram->height(); - _updateTelegram->setGeometry(0, height() - updateHeight, width(), updateHeight); - scrollHeight -= updateHeight; - } + const auto putBottomButton = [&](object_ptr &button) { + if (button) { + const auto buttonHeight = button->height(); + scrollHeight -= buttonHeight; + button->setGeometry( + 0, + scrollTop + scrollHeight, + width(), + buttonHeight); + } + }; + putBottomButton(_updateTelegram); + putBottomButton(_loadMoreChats); auto wasScrollHeight = _scroll->height(); _scroll->setGeometry(0, scrollTop, width(), scrollHeight); if (scrollHeight != wasScrollHeight) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 14dde02b1..6a77a5ff6 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -152,6 +152,7 @@ private: const QVector &dialogs, const QVector &messages); + void setupSupportLoadingLimit(); void setupConnectingWidget(); bool searchForPeersRequired(const QString &query) const; void setSearchInChat(Dialogs::Key chat, UserData *from = nullptr); @@ -166,6 +167,9 @@ private: void updateForwardBar(); void checkUpdateStatus(); + bool loadingBlockedByDate() const; + void refreshLoadMoreButton(); + bool dialogsFailed(const RPCError &error, mtpRequestId req); bool searchFailed(DialogsSearchRequestType type, const RPCError &error, mtpRequestId req); bool peopleFailed(const RPCError &error, mtpRequestId req); @@ -175,7 +179,8 @@ private: QTimer _chooseByDragTimer; bool _dialogsFull = false; - int32 _dialogsOffsetDate = 0; + TimeId _dialogsLoadTill = 0; + TimeId _dialogsOffsetDate = 0; MsgId _dialogsOffsetId = 0; PeerData *_dialogsOffsetPeer = nullptr; mtpRequestId _dialogsRequestId = 0; @@ -191,8 +196,9 @@ private: object_ptr _lockUnlock; object_ptr _scroll; QPointer _inner; - class UpdateButton; - object_ptr _updateTelegram = { nullptr }; + class BottomButton; + object_ptr _updateTelegram = { nullptr }; + object_ptr _loadMoreChats = { nullptr }; base::unique_qptr _connecting; Animation _a_show; diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index 8b1ce0619..d5946c445 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -908,30 +908,14 @@ void SetupThemeOptions(not_null container) { AddSkip(container); } -void SetupSupport(not_null container) { - AddSkip(container); - - AddSubsectionTitle(container, rpl::single(qsl("Support settings"))); - - AddSkip(container, st::settingsSendTypeSkip); - +void SetupSupportSwitchSettings(not_null container) { using SwitchType = Support::SwitchSettings; - - const auto skip = st::settingsSendTypeSkip; - auto wrap = object_ptr(container); - const auto inner = wrap.data(); - container->add( - object_ptr( - container, - std::move(wrap), - QMargins(0, skip, 0, skip))); - const auto group = std::make_shared>( Auth().settings().supportSwitch()); const auto add = [&](SwitchType value, const QString &label) { - inner->add( + container->add( object_ptr>( - inner, + container, group, value, label, @@ -945,6 +929,62 @@ void SetupSupport(not_null container) { Auth().settings().setSupportSwitch(value); Local::writeUserSettings(); }); +} + +void SetupSupportChatsLimitSlice(not_null container) { + constexpr auto kDayDuration = 24 * 60 * 60; + struct Option { + int days = 0; + QString label; + }; + const auto options = std::vector